Fossil SCM
Check the certificate of an https server given by IP address against the certificate's IP addresses. Don't send a TLS server name for an IP address or a non-ASCII host name.
Commit
6753e74e26c9167c2dc573453653980bb365ec93d659de22a8949bd74765c5dd
Parent
af496edce570b1c…
1 file changed
+41
-2
+41
-2
| --- src/http_ssl.c | ||
| +++ src/http_ssl.c | ||
| @@ -35,10 +35,11 @@ | ||
| 35 | 35 | |
| 36 | 36 | #include <openssl/bio.h> |
| 37 | 37 | #include <openssl/ssl.h> |
| 38 | 38 | #include <openssl/err.h> |
| 39 | 39 | #include <openssl/x509.h> |
| 40 | +#include <openssl/x509v3.h> | |
| 40 | 41 | |
| 41 | 42 | #include <assert.h> |
| 42 | 43 | #include <sys/types.h> |
| 43 | 44 | |
| 44 | 45 | /* |
| @@ -436,10 +437,39 @@ | ||
| 436 | 437 | ** real server or a man-in-the-middle imposter. |
| 437 | 438 | */ |
| 438 | 439 | void ssl_disable_cert_verification(void){ |
| 439 | 440 | sslNoCertVerify = 1; |
| 440 | 441 | } |
| 442 | + | |
| 443 | +/* | |
| 444 | +** Return true if zHost is an IPv4 or IPv6 address. A certificate | |
| 445 | +** lists an IP address separately from its DNS names, and RFC 6066 does not | |
| 446 | +** permit an IP address as the TLS server name. | |
| 447 | +*/ | |
| 448 | +static int ssl_host_is_ip_address(const char *zHost){ | |
| 449 | + ASN1_OCTET_STRING *pIp = a2i_IPADDRESS(zHost); | |
| 450 | + if( pIp==0 ){ | |
| 451 | + ERR_clear_error(); | |
| 452 | + return 0; | |
| 453 | + } | |
| 454 | + ASN1_OCTET_STRING_free(pIp); | |
| 455 | + return 1; | |
| 456 | +} | |
| 457 | + | |
| 458 | +/* | |
| 459 | +** Return true if zHost contains a byte outside ASCII. RFC 6066 permits | |
| 460 | +** only an ASCII DNS name as the TLS server name, so an internationalized | |
| 461 | +** domain name would have to be sent in its "xn--" form, but Fossil does not | |
| 462 | +** use that form. | |
| 463 | +*/ | |
| 464 | +static int ssl_host_is_non_ascii(const char *zHost){ | |
| 465 | + while( *zHost ){ | |
| 466 | + if( (unsigned char)*zHost >= 0x80 ) return 1; | |
| 467 | + zHost++; | |
| 468 | + } | |
| 469 | + return 0; | |
| 470 | +} | |
| 441 | 471 | |
| 442 | 472 | /* |
| 443 | 473 | ** Open an SSL connection as a client that is to connect to the server |
| 444 | 474 | ** identified by pUrlData. |
| 445 | 475 | ** |
| @@ -452,10 +482,11 @@ | ||
| 452 | 482 | ** Return the number of errors. |
| 453 | 483 | */ |
| 454 | 484 | int ssl_open_client(UrlData *pUrlData){ |
| 455 | 485 | X509 *cert; |
| 456 | 486 | const char *zRemoteHost; |
| 487 | + int isIpAddr; /* True if zRemoteHost is an IP address */ | |
| 457 | 488 | BIO *sBio; |
| 458 | 489 | |
| 459 | 490 | ssl_global_init_client(); |
| 460 | 491 | if( socket_open(pUrlData) ){ |
| 461 | 492 | ssl_set_errmsg("SSL: cannot open socket (%s)", socket_errmsg()); |
| @@ -477,13 +508,16 @@ | ||
| 477 | 508 | BIO_set_ssl(sBio, ssl, BIO_NOCLOSE); |
| 478 | 509 | BIO_set_ssl_mode(iBio, 1); |
| 479 | 510 | BIO_get_ssl(iBio, &ssl); |
| 480 | 511 | |
| 481 | 512 | zRemoteHost = pUrlData->useProxy ? pUrlData->hostname : pUrlData->name; |
| 513 | + isIpAddr = ssl_host_is_ip_address(zRemoteHost); | |
| 482 | 514 | |
| 483 | 515 | #if (SSLEAY_VERSION_NUMBER >= 0x00908070) && !defined(OPENSSL_NO_TLSEXT) |
| 484 | - if( !SSL_set_tlsext_host_name(ssl, zRemoteHost)){ | |
| 516 | + if( !isIpAddr && !ssl_host_is_non_ascii(zRemoteHost) | |
| 517 | + && !SSL_set_tlsext_host_name(ssl, zRemoteHost) | |
| 518 | + ){ | |
| 485 | 519 | fossil_warning("WARNING: failed to set server name indication (SNI), " |
| 486 | 520 | "continuing without it.\n"); |
| 487 | 521 | } |
| 488 | 522 | #endif |
| 489 | 523 | |
| @@ -490,11 +524,16 @@ | ||
| 490 | 524 | SSL_set_mode(ssl, SSL_MODE_AUTO_RETRY); |
| 491 | 525 | #if OPENSSL_VERSION_NUMBER >= 0x010002000 |
| 492 | 526 | if( !sslNoCertVerify ){ |
| 493 | 527 | X509_VERIFY_PARAM *param = 0; |
| 494 | 528 | param = SSL_get0_param(ssl); |
| 495 | - if( !X509_VERIFY_PARAM_set1_host(param, zRemoteHost, strlen(zRemoteHost)) ){ | |
| 529 | + if( isIpAddr ){ | |
| 530 | + if( !X509_VERIFY_PARAM_set1_ip_asc(param, zRemoteHost) ){ | |
| 531 | + fossil_fatal("failed to set IP address."); | |
| 532 | + } | |
| 533 | + }else if( !X509_VERIFY_PARAM_set1_host(param, zRemoteHost, | |
| 534 | + strlen(zRemoteHost)) ){ | |
| 496 | 535 | fossil_fatal("failed to set hostname."); |
| 497 | 536 | } |
| 498 | 537 | /* SSL_set_verify(ssl, SSL_VERIFY_PEER, 0); */ |
| 499 | 538 | } |
| 500 | 539 | #endif |
| 501 | 540 |
| --- src/http_ssl.c | |
| +++ src/http_ssl.c | |
| @@ -35,10 +35,11 @@ | |
| 35 | |
| 36 | #include <openssl/bio.h> |
| 37 | #include <openssl/ssl.h> |
| 38 | #include <openssl/err.h> |
| 39 | #include <openssl/x509.h> |
| 40 | |
| 41 | #include <assert.h> |
| 42 | #include <sys/types.h> |
| 43 | |
| 44 | /* |
| @@ -436,10 +437,39 @@ | |
| 436 | ** real server or a man-in-the-middle imposter. |
| 437 | */ |
| 438 | void ssl_disable_cert_verification(void){ |
| 439 | sslNoCertVerify = 1; |
| 440 | } |
| 441 | |
| 442 | /* |
| 443 | ** Open an SSL connection as a client that is to connect to the server |
| 444 | ** identified by pUrlData. |
| 445 | ** |
| @@ -452,10 +482,11 @@ | |
| 452 | ** Return the number of errors. |
| 453 | */ |
| 454 | int ssl_open_client(UrlData *pUrlData){ |
| 455 | X509 *cert; |
| 456 | const char *zRemoteHost; |
| 457 | BIO *sBio; |
| 458 | |
| 459 | ssl_global_init_client(); |
| 460 | if( socket_open(pUrlData) ){ |
| 461 | ssl_set_errmsg("SSL: cannot open socket (%s)", socket_errmsg()); |
| @@ -477,13 +508,16 @@ | |
| 477 | BIO_set_ssl(sBio, ssl, BIO_NOCLOSE); |
| 478 | BIO_set_ssl_mode(iBio, 1); |
| 479 | BIO_get_ssl(iBio, &ssl); |
| 480 | |
| 481 | zRemoteHost = pUrlData->useProxy ? pUrlData->hostname : pUrlData->name; |
| 482 | |
| 483 | #if (SSLEAY_VERSION_NUMBER >= 0x00908070) && !defined(OPENSSL_NO_TLSEXT) |
| 484 | if( !SSL_set_tlsext_host_name(ssl, zRemoteHost)){ |
| 485 | fossil_warning("WARNING: failed to set server name indication (SNI), " |
| 486 | "continuing without it.\n"); |
| 487 | } |
| 488 | #endif |
| 489 | |
| @@ -490,11 +524,16 @@ | |
| 490 | SSL_set_mode(ssl, SSL_MODE_AUTO_RETRY); |
| 491 | #if OPENSSL_VERSION_NUMBER >= 0x010002000 |
| 492 | if( !sslNoCertVerify ){ |
| 493 | X509_VERIFY_PARAM *param = 0; |
| 494 | param = SSL_get0_param(ssl); |
| 495 | if( !X509_VERIFY_PARAM_set1_host(param, zRemoteHost, strlen(zRemoteHost)) ){ |
| 496 | fossil_fatal("failed to set hostname."); |
| 497 | } |
| 498 | /* SSL_set_verify(ssl, SSL_VERIFY_PEER, 0); */ |
| 499 | } |
| 500 | #endif |
| 501 |
| --- src/http_ssl.c | |
| +++ src/http_ssl.c | |
| @@ -35,10 +35,11 @@ | |
| 35 | |
| 36 | #include <openssl/bio.h> |
| 37 | #include <openssl/ssl.h> |
| 38 | #include <openssl/err.h> |
| 39 | #include <openssl/x509.h> |
| 40 | #include <openssl/x509v3.h> |
| 41 | |
| 42 | #include <assert.h> |
| 43 | #include <sys/types.h> |
| 44 | |
| 45 | /* |
| @@ -436,10 +437,39 @@ | |
| 437 | ** real server or a man-in-the-middle imposter. |
| 438 | */ |
| 439 | void ssl_disable_cert_verification(void){ |
| 440 | sslNoCertVerify = 1; |
| 441 | } |
| 442 | |
| 443 | /* |
| 444 | ** Return true if zHost is an IPv4 or IPv6 address. A certificate |
| 445 | ** lists an IP address separately from its DNS names, and RFC 6066 does not |
| 446 | ** permit an IP address as the TLS server name. |
| 447 | */ |
| 448 | static int ssl_host_is_ip_address(const char *zHost){ |
| 449 | ASN1_OCTET_STRING *pIp = a2i_IPADDRESS(zHost); |
| 450 | if( pIp==0 ){ |
| 451 | ERR_clear_error(); |
| 452 | return 0; |
| 453 | } |
| 454 | ASN1_OCTET_STRING_free(pIp); |
| 455 | return 1; |
| 456 | } |
| 457 | |
| 458 | /* |
| 459 | ** Return true if zHost contains a byte outside ASCII. RFC 6066 permits |
| 460 | ** only an ASCII DNS name as the TLS server name, so an internationalized |
| 461 | ** domain name would have to be sent in its "xn--" form, but Fossil does not |
| 462 | ** use that form. |
| 463 | */ |
| 464 | static int ssl_host_is_non_ascii(const char *zHost){ |
| 465 | while( *zHost ){ |
| 466 | if( (unsigned char)*zHost >= 0x80 ) return 1; |
| 467 | zHost++; |
| 468 | } |
| 469 | return 0; |
| 470 | } |
| 471 | |
| 472 | /* |
| 473 | ** Open an SSL connection as a client that is to connect to the server |
| 474 | ** identified by pUrlData. |
| 475 | ** |
| @@ -452,10 +482,11 @@ | |
| 482 | ** Return the number of errors. |
| 483 | */ |
| 484 | int ssl_open_client(UrlData *pUrlData){ |
| 485 | X509 *cert; |
| 486 | const char *zRemoteHost; |
| 487 | int isIpAddr; /* True if zRemoteHost is an IP address */ |
| 488 | BIO *sBio; |
| 489 | |
| 490 | ssl_global_init_client(); |
| 491 | if( socket_open(pUrlData) ){ |
| 492 | ssl_set_errmsg("SSL: cannot open socket (%s)", socket_errmsg()); |
| @@ -477,13 +508,16 @@ | |
| 508 | BIO_set_ssl(sBio, ssl, BIO_NOCLOSE); |
| 509 | BIO_set_ssl_mode(iBio, 1); |
| 510 | BIO_get_ssl(iBio, &ssl); |
| 511 | |
| 512 | zRemoteHost = pUrlData->useProxy ? pUrlData->hostname : pUrlData->name; |
| 513 | isIpAddr = ssl_host_is_ip_address(zRemoteHost); |
| 514 | |
| 515 | #if (SSLEAY_VERSION_NUMBER >= 0x00908070) && !defined(OPENSSL_NO_TLSEXT) |
| 516 | if( !isIpAddr && !ssl_host_is_non_ascii(zRemoteHost) |
| 517 | && !SSL_set_tlsext_host_name(ssl, zRemoteHost) |
| 518 | ){ |
| 519 | fossil_warning("WARNING: failed to set server name indication (SNI), " |
| 520 | "continuing without it.\n"); |
| 521 | } |
| 522 | #endif |
| 523 | |
| @@ -490,11 +524,16 @@ | |
| 524 | SSL_set_mode(ssl, SSL_MODE_AUTO_RETRY); |
| 525 | #if OPENSSL_VERSION_NUMBER >= 0x010002000 |
| 526 | if( !sslNoCertVerify ){ |
| 527 | X509_VERIFY_PARAM *param = 0; |
| 528 | param = SSL_get0_param(ssl); |
| 529 | if( isIpAddr ){ |
| 530 | if( !X509_VERIFY_PARAM_set1_ip_asc(param, zRemoteHost) ){ |
| 531 | fossil_fatal("failed to set IP address."); |
| 532 | } |
| 533 | }else if( !X509_VERIFY_PARAM_set1_host(param, zRemoteHost, |
| 534 | strlen(zRemoteHost)) ){ |
| 535 | fossil_fatal("failed to set hostname."); |
| 536 | } |
| 537 | /* SSL_set_verify(ssl, SSL_VERIFY_PEER, 0); */ |
| 538 | } |
| 539 | #endif |
| 540 |