in src/streams/openssl.c [484:615]
static int verify_server_cert(SSL *ssl, const char *host)
{
X509 *cert = NULL;
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 = NULL;
int i = -1, j, error = 0;
if (SSL_get_verify_result(ssl) != X509_V_OK) {
git_error_set(GIT_ERROR_SSL, "the SSL certificate is invalid");
return GIT_ECERTIFICATE;
}
/* Try to parse the host as an IP address to see if it is */
if (p_inet_pton(AF_INET, host, &addr4)) {
type = GEN_IPADD;
addr = &addr4;
} else {
if (p_inet_pton(AF_INET6, host, &addr6)) {
type = GEN_IPADD;
addr = &addr6;
}
}
cert = SSL_get_peer_certificate(ssl);
if (!cert) {
error = -1;
git_error_set(GIT_ERROR_SSL, "the server did not provide a certificate");
goto cleanup;
}
/* 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_get0_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 = addr && !!memcmp(name, addr, namelen);
}
}
}
GENERAL_NAMES_free(alts);
if (matched == 0)
goto cert_fail_name;
if (matched == 1) {
goto cleanup;
}
/* 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);
GIT_ERROR_CHECK_ALLOC(peer_cn);
memcpy(peer_cn, ASN1_STRING_get0_data(str), size);
peer_cn[size] = '\0';
} else {
goto cert_fail_name;
}
} else {
int size = ASN1_STRING_to_UTF8(&peer_cn, str);
GIT_ERROR_CHECK_ALLOC(peer_cn);
if (memchr(peer_cn, '\0', size))
goto cert_fail_name;
}
if (check_host_name((char *)peer_cn, host) < 0)
goto cert_fail_name;
goto cleanup;
cert_fail_name:
error = GIT_ECERTIFICATE;
git_error_set(GIT_ERROR_SSL, "hostname does not match certificate");
goto cleanup;
on_error:
error = ssl_set_error(ssl, 0);
goto cleanup;
cleanup:
X509_free(cert);
OPENSSL_free(peer_cn);
return error;
}