From: Guus Sliepen Date: Sun, 9 Jan 2005 21:30:16 +0000 (+0000) Subject: Make tunnel library actually work with X.509 certificate authentication. X-Git-Url: https://git.tinc-vpn.org/git/browse?a=commitdiff_plain;h=d1cb8f5355ab449dffe10abf81402e5303d5721a;p=tinc Make tunnel library actually work with X.509 certificate authentication. --- diff --git a/tnl/tnl.c b/tnl/tnl.c index 91706b2c..75f82f7c 100644 --- a/tnl/tnl.c +++ b/tnl/tnl.c @@ -23,7 +23,9 @@ #include "system.h" #include +#include +#include "logger/logger.h" #include "support/avl.h" #include "support/sockaddr.h" #include "support/xalloc.h" @@ -43,7 +45,8 @@ static bool tnl_send(tnl_t *tnl, const void *buf, int len) { else logger(LOG_INFO, _("tnl: connection closed by peer")); - tnl->error(tnl, result); + if(tnl->error) + tnl->error(tnl, result); tnl->close(tnl); return !result; } @@ -56,10 +59,10 @@ static bool tnl_send(tnl_t *tnl, const void *buf, int len) { } static bool tnl_recv(tnl_t *tnl) { - int result; tnl_record_t *record = (tnl_record_t *)tnl->buf; - result = gnutls_record_recv(tnl->session, tnl->buf + tnl->bufread, sizeof tnl->buf - tnl->bufread); +#if 0 + int result = gnutls_record_recv(tnl->session, tnl->buf + tnl->bufread, sizeof tnl->buf - tnl->bufread); if(result <= 0) { if(result == GNUTLS_E_INTERRUPTED || result == GNUTLS_E_AGAIN) return true; @@ -69,26 +72,31 @@ static bool tnl_recv(tnl_t *tnl) { else logger(LOG_INFO, _("tnl: connection closed by peer")); - tnl->error(tnl, result); + if(tnl->error) + tnl->error(tnl, result); tnl->close(tnl); return !result; } tnl->bufread += result; +#endif while(tnl->bufread >= sizeof *record && tnl->bufread - sizeof *record >= record->len) { switch(record->type) { case TNL_RECORD_META: - tnl->recv_meta(tnl, record->data, record->len); + if(tnl->recv_meta) + tnl->recv_meta(tnl, record->data, record->len); break; case TNL_RECORD_PACKET: - tnl->recv_packet(tnl, record->data, record->len); + if(tnl->recv_packet) + tnl->recv_packet(tnl, record->data, record->len); break; default: logger(LOG_ERR, _("tnl: error while receiving: %s"), _("unknown record type")); - tnl->error(tnl, EINVAL); + if(tnl->error) + tnl->error(tnl, EINVAL); tnl->close(tnl); return false; } @@ -96,17 +104,31 @@ static bool tnl_recv(tnl_t *tnl) { tnl->bufread -= sizeof *record + record->len; memmove(tnl->buf, record->data + record->len, tnl->bufread); } + + return true; } static bool tnl_recv_handler(fd_t *fd) { + if(!fd) + abort(); + tnl_t *tnl = fd->data; int result; result = gnutls_record_recv(tnl->session, tnl->buf + tnl->bufread, sizeof(tnl->buf) - tnl->bufread); - if(result < 0) { + if(result <= 0) { + if(!result) { + logger(LOG_DEBUG, _("tnl: connection closed by peer %s (%s)"), tnl->remote.id, tnl->remote.hostname); + if(tnl->error) + tnl->error(tnl, 0); + tnl->close(tnl); + return false; + } + if(gnutls_error_is_fatal(result)) { - logger(LOG_DEBUG, _("tnl: reception failed: %s\n"), gnutls_strerror(result)); - tnl->error(tnl, result); + logger(LOG_DEBUG, _("tnl: reception failed: %s"), gnutls_strerror(result)); + if(tnl->error) + tnl->error(tnl, result); tnl->close(tnl); return false; } @@ -118,11 +140,81 @@ static bool tnl_recv_handler(fd_t *fd) { return tnl_recv(tnl); } -static bool tnl_authenticate(tnl_t *tnl) { +bool tnl_ep_set_x509_credentials(tnl_ep_t *tnl_ep, const char *privkey, const char *certificate, const char *trust, const char *crl) { + int err; + + if(tnl_ep->cred.certificate) { + gnutls_certificate_free_credentials(tnl_ep->cred.certificate); + tnl_ep->cred.certificate = NULL; + } + + if((err = gnutls_certificate_allocate_credentials(&tnl_ep->cred.certificate)) < 0) { + logger(LOG_ERR, _("Failed to allocate certificate credentials: %s"), gnutls_strerror(err)); + return false; + } + + if((err = gnutls_certificate_set_x509_key_file(tnl_ep->cred.certificate, certificate, privkey, GNUTLS_X509_FMT_PEM)) < 0) { + logger(LOG_ERR, _("Failed to load X.509 key and/or certificate: %s"), gnutls_strerror(err)); + return false; + } + + tnl_ep->cred.type = GNUTLS_CRD_CERTIFICATE; + + if(trust && (err = gnutls_certificate_set_x509_trust_file(tnl_ep->cred.certificate, trust, GNUTLS_X509_FMT_PEM)) < 0) { + logger(LOG_ERR, _("Failed to set X.509 trust file: %s"), gnutls_strerror(err)); + return false; + } + + if(crl && (err = gnutls_certificate_set_x509_crl_file(tnl_ep->cred.certificate, crl, GNUTLS_X509_FMT_PEM)) < 0) { + logger(LOG_ERR, _("Failed to set X.509 CRL file: %s"), gnutls_strerror(err)); + return false; + } + + //gnutls_certificate_set_verify_flags(tnl_ep->cred.certificate, GNUTLS_VERIFY_DISABLE_CA_SIGN); + + return true; +} + +bool tnl_ep_set_openpgp_credentials(tnl_ep_t *tnl_ep, const char *privkey, const char *pubkey, const char *keyring, const char *trustdb) { + int err; + + if(tnl_ep->cred.certificate) { + gnutls_certificate_free_credentials(tnl_ep->cred.certificate); + tnl_ep->cred.certificate = NULL; + } + + if((err = gnutls_certificate_allocate_credentials(&tnl_ep->cred.certificate)) < 0) { + logger(LOG_ERR, _("Failed to allocate certificate credentials: %s"), gnutls_strerror(err)); + return false; + } + + if((err = gnutls_certificate_set_openpgp_key_file(tnl_ep->cred.certificate, pubkey, privkey)) < 0) { + logger(LOG_ERR, _("Failed to load public and/or private OpenPGP key: %s"), gnutls_strerror(err)); + return false; + } + + tnl_ep->cred.type = GNUTLS_CRD_CERTIFICATE; + + if(keyring && (err = gnutls_certificate_set_openpgp_keyring_file(tnl_ep->cred.certificate, keyring)) < 0) { + logger(LOG_ERR, _("Failed to set OpenPGP keyring file: %s"), gnutls_strerror(err)); + return false; + } + + if(trustdb && (err = gnutls_certificate_set_openpgp_trustdb(tnl_ep->cred.certificate, trustdb)) < 0) { + logger(LOG_ERR, _("Failed to set OpenPGP trustdb file: %s"), gnutls_strerror(err)); + return false; + } + + //gnutls_certificate_set_verify_flags(tnl_ep->cred.certificate, GNUTLS_VERIFY_DISABLE_CA_SIGN); + + return true; +} + +static bool tnl_authenticate_x509(tnl_t *tnl) { gnutls_x509_crt cert; const gnutls_datum *certs; int ncerts = 0, result; - char buf[1024], *name, *p; + char name[1024]; int len; certs = gnutls_certificate_get_peers(tnl->session, &ncerts); @@ -132,38 +224,40 @@ static bool tnl_authenticate(tnl_t *tnl) { return false; } - len = sizeof buf; gnutls_x509_crt_init(&cert); - result = gnutls_x509_crt_import(cert, certs, GNUTLS_X509_FMT_DER) ?: gnutls_x509_crt_get_dn(cert, buf, &len); + result = gnutls_x509_crt_import(cert, certs, GNUTLS_X509_FMT_DER); if(result) { - logger(LOG_ERR, _("tnl: error importing certificate from %s: %s"), tnl->remote.hostname, gnutls_strerror(errno)); + logger(LOG_ERR, _("tnl: error importing certificate from %s: %s"), tnl->remote.hostname, gnutls_strerror(result)); gnutls_x509_crt_deinit(cert); return false; } - name = strstr(buf, "CN="); - if(!name) { - logger(LOG_ERR, _("tnl: no name in certificate from %s"), tnl->remote.hostname); - gnutls_x509_crt_deinit(cert); + len = sizeof name; + result = gnutls_x509_crt_get_dn_by_oid(cert, GNUTLS_OID_X520_COMMON_NAME, 0, 0, name, &len); + gnutls_x509_crt_deinit(cert); + + if(result) { + logger(LOG_ERR, _("tnl: could not extract common name from certificate from %s: %s"), tnl->remote.hostname, gnutls_strerror(result)); return false; } - name += 3; - for(p = name; *p && *p != ','; p++); - *p = '\0'; + if(len > sizeof name) { + logger(LOG_ERR, _("tnl: common name from certificate from %s too long"), tnl->remote.hostname); + return false; + } if(tnl->remote.id && strcmp(tnl->remote.id, name)) { logger(LOG_ERR, _("tnl: peer %s is %s instead of %s"), tnl->remote.hostname, name, tnl->remote.id); return false; } - replace(tnl->remote.id, name); + replace(tnl->remote.id, xstrdup(name)); result = gnutls_certificate_verify_peers(tnl->session); if(result < 0) { - logger(LOG_ERR, "tnl: error verifying certificate from %s (%s): %s\n", tnl->remote.id, tnl->remote.hostname, gnutls_strerror(result)); + logger(LOG_ERR, "tnl: error verifying certificate from %s (%s): %s", tnl->remote.id, tnl->remote.hostname, gnutls_strerror(result)); return false; } @@ -171,19 +265,46 @@ static bool tnl_authenticate(tnl_t *tnl) { logger(LOG_ERR, "tnl: certificate from %s (%s) not good, verification result %x", tnl->remote.id, tnl->remote.hostname, result); return false; } + + return true; } - +static bool tnl_authenticate(tnl_t *tnl) { + switch(tnl->local.cred.type) { + case GNUTLS_CRD_CERTIFICATE: + switch(gnutls_certificate_type_get(tnl->session)) { + case GNUTLS_CRT_X509: + return tnl_authenticate_x509(tnl); + case GNUTLS_CRT_OPENPGP: + //return tnl_authenticate_openpgp(tnl); + default: + logger(LOG_ERR, "tnl: unknown certificate type for session with %s (%s)", tnl->remote.id, tnl->remote.hostname); + return false; + } + + case GNUTLS_CRD_ANON: + logger(LOG_ERR, "tnl: anonymous authentication not yet supported"); + return false; + + case GNUTLS_CRD_SRP: + logger(LOG_ERR, "tnl: SRP authentication not yet supported"); + return false; + + default: + logger(LOG_ERR, "tnl: unknown authentication type for session with %s (%s)", tnl->remote.id, tnl->remote.hostname); + return false; + } +} static bool tnl_handshake_handler(fd_t *fd) { - char id[1024]; + //char id[1024]; tnl_t *tnl = fd->data; int result; result = gnutls_handshake(tnl->session); if(result < 0) { if(gnutls_error_is_fatal(result)) { - logger(LOG_ERR, "tnl: handshake error: %s\n", gnutls_strerror(result)); + logger(LOG_ERR, "tnl: handshake error: %s", gnutls_strerror(result)); tnl->close(tnl); return false; } @@ -197,9 +318,11 @@ static bool tnl_handshake_handler(fd_t *fd) { if(!tnl_authenticate(tnl)) return false; - tnl->status == TNL_STATUS_UP; + tnl->status = TNL_STATUS_UP; tnl->fd.read = tnl_recv_handler; - tnl->accept(tnl); + if(tnl->accept) + tnl->accept(tnl); + return true; } @@ -233,11 +356,6 @@ static bool tnl_close(tnl_t *tnl) { return true; } -static bool tnl_accept_error(tnl_t *tnl, int errnum) { - logger(LOG_ERR, _("tnl: error %d on accepted tunnel")); - return true; -} - static bool tnl_accept_handler(fd_t *fd) { tnl_listen_t *listener = fd->data; tnl_t *tnl; @@ -252,9 +370,9 @@ static bool tnl_accept_handler(fd_t *fd) { return false; } - logger(LOG_DEBUG, _("tnl: accepted incoming connection")); - sa_unmap(&ss); + + logger(LOG_DEBUG, _("tnl: accepted incoming connection")); clear(new(tnl)); tnl->local = listener->local; @@ -264,8 +382,8 @@ static bool tnl_accept_handler(fd_t *fd) { sa_unmap(&tnl->local.address); tnl->type = listener->type; tnl->protocol = listener->protocol; - tnl->status = TNL_STATUS_CONNECTING; - tnl->error = tnl_accept_error; + tnl->send_packet = tnl_send_packet; + tnl->send_meta = tnl_send_meta; tnl->close = tnl_close; tnl->fd.fd = sock; @@ -274,17 +392,19 @@ static bool tnl_accept_handler(fd_t *fd) { fcntl(sock, F_SETFL, fcntl(sock, F_GETFL) | O_NONBLOCK); + tnl->status = TNL_STATUS_HANDSHAKE; gnutls_init(&tnl->session, GNUTLS_SERVER); //gnutls_handshake_set_private_extensions(tnl->session, 1); gnutls_set_default_priority(tnl->session); - gnutls_credentials_set(tnl->session, GNUTLS_CRD_CERTIFICATE, tnl->local.cred); + gnutls_credentials_set(tnl->session, tnl->local.cred.type, tnl->local.cred.certificate); gnutls_certificate_server_set_request(tnl->session, GNUTLS_CERT_REQUEST); gnutls_transport_set_ptr(tnl->session, (gnutls_transport_ptr)sock); - gnutls_handshake(tnl->session); tnl->accept = listener->accept; fd_add(&tnl->fd); + + tnl_handshake_handler(&tnl->fd); return true; } @@ -298,28 +418,30 @@ static bool tnl_connect_handler(fd_t *fd) { getsockopt(fd->fd, SOL_SOCKET, SO_ERROR, &result, &len); if(result) { logger(LOG_ERR, "tnl: error while connecting: %s", strerror(result)); - tnl->error(tnl, result); + if(tnl->error) + tnl->error(tnl, result); tnl->close(tnl); return false; } + logger(LOG_DEBUG, _("tnl: connected")); + fcntl(tnl->fd.fd, F_SETFL, fcntl(tnl->fd.fd, F_GETFL) | O_NONBLOCK); tnl->status = TNL_STATUS_HANDSHAKE; gnutls_init(&tnl->session, GNUTLS_CLIENT); //gnutls_handshake_set_private_extensions(tnl->session, 1); gnutls_set_default_priority(tnl->session); - gnutls_credentials_set(tnl->session, GNUTLS_CRD_CERTIFICATE, tnl->local.cred); + gnutls_credentials_set(tnl->session, tnl->local.cred.type, tnl->local.cred.certificate); gnutls_certificate_server_set_request(tnl->session, GNUTLS_CERT_REQUEST); gnutls_transport_set_ptr(tnl->session, (gnutls_transport_ptr)fd->fd); - gnutls_handshake(tnl->session); tnl->fd.write = NULL; tnl->fd.read = tnl_handshake_handler; fd_mod(&tnl->fd); - logger(LOG_DEBUG, _("tnl: connected")); - + tnl_handshake_handler(&tnl->fd); + return true; } diff --git a/tnl/tnl.h b/tnl/tnl.h index 6b184080..77a8b2a0 100644 --- a/tnl/tnl.h +++ b/tnl/tnl.h @@ -24,6 +24,7 @@ #define __TNL_H__ #include +#include #include "fd/fd.h" @@ -47,12 +48,26 @@ typedef enum tnl_status { TNL_STATUS_UP, } tnl_status_t; +typedef struct tnl_ep_credentials { + gnutls_credentials_type type; + union { + gnutls_anon_client_credentials anon_client; + gnutls_anon_server_credentials anon_server; + gnutls_srp_client_credentials srp_client; + gnutls_srp_server_credentials srp_server; + gnutls_certificate_credentials certificate; + }; +} tnl_ep_credentials_t; + +typedef struct tnl_ep_cryptoparm { +} tnl_ep_cryptoparm_t; + typedef struct tnl_ep { struct sockaddr_storage address; char *id; char *hostname; - struct tnl_ep_credentials *cred; - struct tnl_ep_cryptoparm *parm; + struct tnl_ep_credentials cred; + struct tnl_ep_cryptoparm parm; } tnl_ep_t; typedef struct tnl { @@ -75,8 +90,8 @@ typedef struct tnl { /* private */ - struct fd fd; gnutls_session session; + struct fd fd; char buf[4096]; int bufread; } tnl_t; @@ -95,11 +110,7 @@ typedef struct tnl_listen { extern bool tnl_listen(struct tnl_listen *listener); extern bool tnl_connect(struct tnl *tnl); -extern bool tnl_credentials_sprint(const char *buf, int len, const struct tnl_ep_credentials *cred); -extern bool tnl_credentials_sscan(const char *buf, struct tnl_ep_credentials *cred); -extern bool tnl_cryptoparm_sprint(const char *buf, int len, const struct tnl_ep_cryptoparm *parm); -extern bool tnl_cryptoparm_sscan(const char *buf, struct tnl_ep_cryptoparm *parm); -extern bool tnl_credentials_fprint(FILE *stream, const struct tnl_ep_credentials *cred); -extern bool tnl_credentials_fscan(FILE *stream, struct tnl_ep_credentials *cred); +extern bool tnl_ep_set_x509_credentials(tnl_ep_t *tnl_ep, const char *key, const char *certificate, const char *trust, const char *crl); +extern bool tnl_ep_set_openpgp_credentials(tnl_ep_t *tnl_ep, const char *privkey, const char *pubkey, const char *keyring, const char *trustdb); #endif /* __TNL_H__ */