diff options
| author | Vicent Martà <tanoku@gmail.com> | 2012-06-05 13:53:33 -0700 |
|---|---|---|
| committer | Vicent Martà <tanoku@gmail.com> | 2012-06-05 13:53:33 -0700 |
| commit | 966fbdcb8eb5839d3ca41d5b838c1cec1b393b3c (patch) | |
| tree | ce84c7f3ebb7e3e724fc8d56aeeb130abe0b648c /src/netops.c | |
| parent | eadc0e03f12038ec9ef91fe779b7f94b3a32b80d (diff) | |
| parent | 250b95b24b1a079be5825f862e42f4b99a4c3587 (diff) | |
| download | libgit2-966fbdcb8eb5839d3ca41d5b838c1cec1b393b3c.tar.gz | |
Merge pull request #697 from carlosmn/ssl
Add HTTPS support
Diffstat (limited to 'src/netops.c')
| -rw-r--r-- | src/netops.c | 320 |
1 files changed, 313 insertions, 7 deletions
diff --git a/src/netops.c b/src/netops.c index e16cae8e6..705c1d415 100644 --- a/src/netops.c +++ b/src/netops.c @@ -18,13 +18,20 @@ # endif #endif +#ifdef GIT_SSL +# include <openssl/ssl.h> +# include <openssl/x509v3.h> +#endif +#include <ctype.h> +#include <arpa/inet.h> #include "git2/errors.h" #include "common.h" #include "netops.h" #include "posix.h" #include "buffer.h" +#include "transport.h" #ifdef GIT_WIN32 static void net_set_error(const char *str) @@ -45,25 +52,68 @@ static void net_set_error(const char *str) } #endif -void gitno_buffer_setup(gitno_buffer *buf, char *data, unsigned int len, GIT_SOCKET fd) +#ifdef GIT_SSL +static int ssl_set_error(gitno_ssl *ssl, int error) +{ + int err; + err = SSL_get_error(ssl->ssl, error); + giterr_set(GITERR_NET, "SSL error: %s", ERR_error_string(err, NULL)); + return -1; +} +#endif + +void gitno_buffer_setup(git_transport *t, gitno_buffer *buf, char *data, unsigned int len) { memset(buf, 0x0, sizeof(gitno_buffer)); memset(data, 0x0, len); buf->data = data; buf->len = len; buf->offset = 0; - buf->fd = fd; + buf->fd = t->socket; +#ifdef GIT_SSL + if (t->encrypt) + buf->ssl = &t->ssl; +#endif } +#ifdef GIT_SSL +static int ssl_recv(gitno_ssl *ssl, void *data, size_t len) +{ + int ret; + + do { + ret = SSL_read(ssl->ssl, data, len); + } while (SSL_get_error(ssl->ssl, ret) == SSL_ERROR_WANT_READ); + + if (ret < 0) + return ssl_set_error(ssl, ret); + + return ret; +} +#endif + int gitno_recv(gitno_buffer *buf) { int ret; +#ifdef GIT_SSL + if (buf->ssl != NULL) { + if ((ret = ssl_recv(buf->ssl, buf->data + buf->offset, buf->len - buf->offset)) < 0) + return -1; + } else { + ret = p_recv(buf->fd, buf->data + buf->offset, buf->len - buf->offset, 0); + if (ret < 0) { + net_set_error("Error receiving socket data"); + return -1; + } + } +#else ret = p_recv(buf->fd, buf->data + buf->offset, buf->len - buf->offset, 0); if (ret < 0) { net_set_error("Error receiving socket data"); return -1; } +#endif buf->offset += ret; return ret; @@ -92,7 +142,237 @@ void gitno_consume_n(gitno_buffer *buf, size_t cons) buf->offset -= cons; } -int gitno_connect(GIT_SOCKET *sock, const char *host, const char *port) +int gitno_ssl_teardown(git_transport *t) +{ + int ret = ret; + + if (!t->encrypt) + return 0; + +#ifdef GIT_SSL + + do { + ret = SSL_shutdown(t->ssl.ssl); + } while (ret == 0); + if (ret < 0) + return ssl_set_error(&t->ssl, ret); + + SSL_free(t->ssl.ssl); + SSL_CTX_free(t->ssl.ctx); +#endif + return 0; +} + + +#ifdef GIT_SSL +/* Match host names according to RFC 2818 rules */ +static int match_host(const char *pattern, const char *host) +{ + for (;;) { + char c = tolower(*pattern++); + + if (c == '\0') + return *host ? -1 : 0; + + if (c == '*') { + c = *pattern; + /* '*' at the end matches everything left */ + if (c == '\0') + return 0; + + /* + * We've found a pattern, so move towards the next matching + * char. The '.' is handled specially because wildcards aren't + * allowed to cross subdomains. + */ + + while(*host) { + char h = tolower(*host); + if (c == h) + return match_host(pattern, host++); + if (h == '.') + return match_host(pattern, host); + host++; + } + return -1; + } + + if (c != tolower(*host++)) + return -1; + } + + return -1; +} + +static int check_host_name(const char *name, const char *host) +{ + if (!strcasecmp(name, host)) + return 0; + + if (match_host(name, host) < 0) + return -1; + + return 0; +} + +static int verify_server_cert(git_transport *t, const char *host) +{ + X509 *cert; + X509_NAME *peer_name; + ASN1_STRING *str; + unsigned char *peer_cn = NULL; + int matched = -1, type = GEN_DNS; + GENERAL_NAMES *alts; + struct in6_addr addr6; + struct in_addr addr4; + void *addr; + int i = -1,j; + + + /* Try to parse the host as an IP address to see if it is */ + if (inet_pton(AF_INET, host, &addr4)) { + type = GEN_IPADD; + addr = &addr4; + } else { + if(inet_pton(AF_INET6, host, &addr6)) { + type = GEN_IPADD; + addr = &addr6; + } + } + + + cert = SSL_get_peer_certificate(t->ssl.ssl); + + /* Check the alternative names */ + alts = X509_get_ext_d2i(cert, NID_subject_alt_name, NULL, NULL); + if (alts) { + int num; + + num = sk_GENERAL_NAME_num(alts); + for (i = 0; i < num && matched != 1; i++) { + const GENERAL_NAME *gn = sk_GENERAL_NAME_value(alts, i); + const char *name = (char *) ASN1_STRING_data(gn->d.ia5); + size_t namelen = (size_t) ASN1_STRING_length(gn->d.ia5); + + /* Skip any names of a type we're not looking for */ + if (gn->type != type) + continue; + + if (type == GEN_DNS) { + /* If it contains embedded NULs, don't even try */ + if (memchr(name, '\0', namelen)) + continue; + + if (check_host_name(name, host) < 0) + matched = 0; + else + matched = 1; + } else if (type == GEN_IPADD) { + /* Here name isn't so much a name but a binary representation of the IP */ + matched = !!memcmp(name, addr, namelen); + } + } + } + GENERAL_NAMES_free(alts); + + if (matched == 0) + goto on_error; + + if (matched == 1) + return 0; + + /* If no alternative names are available, check the common name */ + peer_name = X509_get_subject_name(cert); + if (peer_name == NULL) + goto on_error; + + if (peer_name) { + /* Get the index of the last CN entry */ + while ((j = X509_NAME_get_index_by_NID(peer_name, NID_commonName, i)) >= 0) + i = j; + } + + if (i < 0) + goto on_error; + + str = X509_NAME_ENTRY_get_data(X509_NAME_get_entry(peer_name, i)); + if (str == NULL) + goto on_error; + + /* Work around a bug in OpenSSL whereby ASN1_STRING_to_UTF8 fails if it's already in utf-8 */ + if (ASN1_STRING_type(str) == V_ASN1_UTF8STRING) { + int size = ASN1_STRING_length(str); + + if (size > 0) { + peer_cn = OPENSSL_malloc(size + 1); + GITERR_CHECK_ALLOC(peer_cn); + memcpy(peer_cn, ASN1_STRING_data(str), size); + peer_cn[size] = '\0'; + } + } else { + int size = ASN1_STRING_to_UTF8(&peer_cn, str); + GITERR_CHECK_ALLOC(peer_cn); + if (memchr(peer_cn, '\0', size)) + goto cert_fail; + } + + if (check_host_name((char *)peer_cn, host) < 0) + goto cert_fail; + + OPENSSL_free(peer_cn); + + return 0; + +on_error: + OPENSSL_free(peer_cn); + return ssl_set_error(&t->ssl, 0); + +cert_fail: + OPENSSL_free(peer_cn); + giterr_set(GITERR_SSL, "Certificate host name check failed"); + return -1; +} + +static int ssl_setup(git_transport *t, const char *host) +{ + int ret; + + SSL_library_init(); + SSL_load_error_strings(); + t->ssl.ctx = SSL_CTX_new(SSLv23_method()); + if (t->ssl.ctx == NULL) + return ssl_set_error(&t->ssl, 0); + + SSL_CTX_set_mode(t->ssl.ctx, SSL_MODE_AUTO_RETRY); + SSL_CTX_set_verify(t->ssl.ctx, SSL_VERIFY_PEER, NULL); + if (!SSL_CTX_set_default_verify_paths(t->ssl.ctx)) + return ssl_set_error(&t->ssl, 0); + + t->ssl.ssl = SSL_new(t->ssl.ctx); + if (t->ssl.ssl == NULL) + return ssl_set_error(&t->ssl, 0); + + if((ret = SSL_set_fd(t->ssl.ssl, t->socket)) == 0) + return ssl_set_error(&t->ssl, ret); + + if ((ret = SSL_connect(t->ssl.ssl)) <= 0) + return ssl_set_error(&t->ssl, ret); + + if (t->check_cert && verify_server_cert(t, host) < 0) + return -1; + + return 0; +} +#else +static int ssl_setup(git_transport *t, const char *host) +{ + GIT_UNUSED(t); + GIT_UNUSED(host); + return 0; +} +#endif + +int gitno_connect(git_transport *t, const char *host, const char *port) { struct addrinfo *info = NULL, *p; struct addrinfo hints; @@ -129,20 +409,46 @@ int gitno_connect(GIT_SOCKET *sock, const char *host, const char *port) return -1; } + t->socket = s; freeaddrinfo(info); - *sock = s; + + if (t->encrypt && ssl_setup(t, host) < 0) + return -1; + return 0; } -int gitno_send(GIT_SOCKET s, const char *msg, size_t len, int flags) +#ifdef GIT_SSL +static int send_ssl(gitno_ssl *ssl, const char *msg, size_t len) { int ret; size_t off = 0; while (off < len) { - errno = 0; + ret = SSL_write(ssl->ssl, msg + off, len - off); + if (ret <= 0) + return ssl_set_error(ssl, ret); - ret = p_send(s, msg + off, len - off, flags); + off += ret; + } + + return off; +} +#endif + +int gitno_send(git_transport *t, const char *msg, size_t len, int flags) +{ + int ret; + size_t off = 0; + +#ifdef GIT_SSL + if (t->encrypt) + return send_ssl(&t->ssl, msg, len); +#endif + + while (off < len) { + errno = 0; + ret = p_send(t->socket, msg + off, len - off, flags); if (ret < 0) { net_set_error("Error sending data"); return -1; |
