diff --git a/src/socket.c b/src/socket.c index 22be813..880e6c5 100644 --- a/src/socket.c +++ b/src/socket.c @@ -28,6 +28,7 @@ # include # include # include +# include #endif #include "isync.h" @@ -118,6 +119,62 @@ compare_certificates( X509 *cert, X509 *peercert, return 0; } +static int +host_matches( const char *host, const char *pattern ) +{ + if (pattern[0] == '*' && pattern[1] == '.') { + pattern += 2; + if (!(host = strchr( host, '.' ))) + return 0; + host++; + } + + return *host && *pattern && !strcasecmp( host, pattern ); +} + +static int +verify_hostname( X509 *cert, const char *hostname ) +{ + int i, len, found; + X509_NAME *subj; + STACK_OF(GENERAL_NAME) *subj_alt_names; + char cname[1000]; + + /* try the DNS subjectAltNames */ + found = 0; + if ((subj_alt_names = X509_get_ext_d2i( cert, NID_subject_alt_name, NULL, NULL ))) { + int num_subj_alt_names = sk_GENERAL_NAME_num( subj_alt_names ); + for (i = 0; i < num_subj_alt_names; i++) { + GENERAL_NAME *subj_alt_name = sk_GENERAL_NAME_value( subj_alt_names, i ); + if (subj_alt_name->type == GEN_DNS && + strlen( (const char *)subj_alt_name->d.ia5->data ) == (size_t)subj_alt_name->d.ia5->length && + host_matches( hostname, (const char *)(subj_alt_name->d.ia5->data) )) + { + found = 1; + break; + } + } + sk_GENERAL_NAME_pop_free( subj_alt_names, GENERAL_NAME_free ); + } + if (found) + return 0; + + /* try the common name */ + if (!(subj = X509_get_subject_name( cert ))) { + error( "Error, cannot get certificate subject\n" ); + return -1; + } + if ((len = X509_NAME_get_text_by_NID( subj, NID_commonName, cname, sizeof(cname) )) < 0) { + error( "Error, cannot get certificate common name\n" ); + return -1; + } + if (strlen( cname ) == (size_t)len && host_matches( hostname, cname )) + return 0; + + error( "Error, certificate owner does not match hostname %s\n", hostname ); + return -1; +} + #if OPENSSL_VERSION_NUMBER >= 0x00904000L #define READ_X509_KEY(fp, key) PEM_read_X509( fp, key, 0, 0 ) #else @@ -146,6 +203,8 @@ verify_cert( const server_conf_t *conf, conn_t *sock ) } while (conf->cert_file) { /* while() instead of if() so break works */ + /* Note: this code intentionally does no hostname verification. */ + if (X509_cmp_current_time( X509_get_notBefore( cert )) >= 0) { error( "Server certificate is not yet valid\n" ); break; @@ -164,7 +223,7 @@ verify_cert( const server_conf_t *conf, conn_t *sock ) } err = -1; for (lcert = 0; READ_X509_KEY( fp, &lcert ); ) - if (!(err = compare_certificates( lcert, cert, md, n ))) + if (!(err = compare_certificates( lcert, cert, md, n ))) /* TODO: check X509v3 [Extended] Key Usage? */ break; X509_free( lcert ); fclose( fp ); @@ -193,11 +252,16 @@ verify_cert( const server_conf_t *conf, conn_t *sock ) X509_STORE_CTX_init( &xsc, mconf->cert_store, cert, 0 ); err = X509_verify_cert( &xsc ) > 0 ? 0 : X509_STORE_CTX_get_error( &xsc ); X509_STORE_CTX_cleanup( &xsc ); - if (!err) - return 0; - error( "Error, cannot verify certificate: %s (%d)\n", - X509_verify_cert_error_string( err ), err ); + if (err) { + error( "Error, cannot verify certificate: %s (%d)\n", + X509_verify_cert_error_string( err ), err ); + goto intcheck; + } + if (conf->host && verify_hostname( cert, conf->host ) < 0) + goto intcheck; + return 0; + intcheck: X509_NAME_oneline( X509_get_subject_name( cert ), buf, sizeof(buf) ); info( "\nSubject: %s\n", buf ); X509_NAME_oneline( X509_get_issuer_name( cert ), buf, sizeof(buf) );