Fossil SCM
Rework the SSL cert exception mechanism so that it remembers the SHA3 hash of the cert that failed to verify, rather than the PEM of the complete cert. Simplify the error prompts. Always verify the cert hash before accepting the exception.
Commit
3c194e2b893134b12156196639c67b0384b672371d5640f8ef0202f70e24d74c
Parent
bc236201210bfee…
1 file changed
+105
-115
+105
-115
| --- src/http_ssl.c | ||
| +++ src/http_ssl.c | ||
| @@ -46,11 +46,15 @@ | ||
| 46 | 46 | static int sslIsInit = 0; /* True after global initialization */ |
| 47 | 47 | static BIO *iBio = 0; /* OpenSSL I/O abstraction */ |
| 48 | 48 | static char *sslErrMsg = 0; /* Text of most recent OpenSSL error */ |
| 49 | 49 | static SSL_CTX *sslCtx; /* SSL context */ |
| 50 | 50 | static SSL *ssl; |
| 51 | - | |
| 51 | +static struct { /* Accept this SSL cert for this session only */ | |
| 52 | + char *zHost; /* Subject or host name */ | |
| 53 | + char *zHash; /* SHA3 hash of the cert */ | |
| 54 | +} sException; | |
| 55 | +static int sslNoCertVerify = 0; /* Do not verify SSL certs */ | |
| 52 | 56 | |
| 53 | 57 | /* |
| 54 | 58 | ** Clear the SSL error message |
| 55 | 59 | */ |
| 56 | 60 | static void ssl_clear_errmsg(void){ |
| @@ -224,10 +228,21 @@ | ||
| 224 | 228 | }while(!done); |
| 225 | 229 | sscanf(bbuf, "HTTP/1.%d %d", &httpVerMin, &rc); |
| 226 | 230 | blob_reset(&reply); |
| 227 | 231 | return rc; |
| 228 | 232 | } |
| 233 | + | |
| 234 | +/* | |
| 235 | +** Invoke this routine to disable SSL cert verification. After | |
| 236 | +** this call is made, any SSL cert that the server provides will | |
| 237 | +** be accepted. Communication will still be encrypted, but the | |
| 238 | +** client has no way of knowing whether it is talking to the | |
| 239 | +** real server or a man-in-the-middle imposter. | |
| 240 | +*/ | |
| 241 | +int ssl_disable_cert_verification(void){ | |
| 242 | + sslNoCertVerify = 1; | |
| 243 | +} | |
| 229 | 244 | |
| 230 | 245 | /* |
| 231 | 246 | ** Open an SSL connection. The identify of the server is determined |
| 232 | 247 | ** as follows: |
| 233 | 248 | ** |
| @@ -237,26 +252,13 @@ | ||
| 237 | 252 | ** |
| 238 | 253 | ** Return the number of errors. |
| 239 | 254 | */ |
| 240 | 255 | int ssl_open(UrlData *pUrlData){ |
| 241 | 256 | X509 *cert; |
| 242 | - int hasSavedCertificate = 0; | |
| 243 | - int trusted = 0; | |
| 244 | 257 | unsigned long e; |
| 245 | 258 | |
| 246 | 259 | ssl_global_init(); |
| 247 | - | |
| 248 | - /* Get certificate for current server from global config and | |
| 249 | - * (if we have it in config) add it to certificate store. | |
| 250 | - */ | |
| 251 | - cert = ssl_get_certificate(pUrlData, &trusted); | |
| 252 | - if ( cert!=NULL ){ | |
| 253 | - X509_STORE_add_cert(SSL_CTX_get_cert_store(sslCtx), cert); | |
| 254 | - X509_free(cert); | |
| 255 | - hasSavedCertificate = 1; | |
| 256 | - } | |
| 257 | - | |
| 258 | 260 | if( pUrlData->useProxy ){ |
| 259 | 261 | int rc; |
| 260 | 262 | char *connStr = mprintf("%s:%d", g.url.name, pUrlData->port); |
| 261 | 263 | BIO *sBio = BIO_new_connect(connStr); |
| 262 | 264 | free(connStr); |
| @@ -326,70 +328,65 @@ | ||
| 326 | 328 | ssl_set_errmsg("No SSL certificate was presented by the peer"); |
| 327 | 329 | ssl_close(); |
| 328 | 330 | return 1; |
| 329 | 331 | } |
| 330 | 332 | |
| 331 | - if( trusted<=0 && (e = SSL_get_verify_result(ssl)) != X509_V_OK ){ | |
| 333 | + if( !sslNoCertVerify && SSL_get_verify_result(ssl)!=X509_V_OK ){ | |
| 332 | 334 | char *desc, *prompt; |
| 333 | - const char *warning = ""; | |
| 334 | 335 | Blob ans; |
| 335 | 336 | char cReply; |
| 336 | 337 | BIO *mem; |
| 337 | 338 | unsigned char md[32]; |
| 338 | - unsigned int mdLength = 31; | |
| 339 | - | |
| 340 | - mem = BIO_new(BIO_s_mem()); | |
| 341 | - X509_NAME_print_ex(mem, X509_get_subject_name(cert), 2, XN_FLAG_MULTILINE); | |
| 342 | - BIO_puts(mem, "\n\nIssued By:\n\n"); | |
| 343 | - X509_NAME_print_ex(mem, X509_get_issuer_name(cert), 2, XN_FLAG_MULTILINE); | |
| 344 | - BIO_puts(mem, "\n\nSHA1 Fingerprint:\n\n "); | |
| 345 | - if(X509_digest(cert, EVP_sha1(), md, &mdLength)){ | |
| 346 | - int j; | |
| 347 | - for( j = 0; j < mdLength; ++j ) { | |
| 348 | - BIO_printf(mem, " %02x", md[j]); | |
| 349 | - } | |
| 350 | - } | |
| 351 | - BIO_write(mem, "", 1); /* nul-terminate mem buffer */ | |
| 352 | - BIO_get_mem_data(mem, &desc); | |
| 353 | - | |
| 354 | - if( hasSavedCertificate ){ | |
| 355 | - warning = "WARNING: Certificate doesn't match the " | |
| 356 | - "saved certificate for this host!"; | |
| 357 | - } | |
| 358 | - prompt = mprintf("\nSSL verification failed: %s\n" | |
| 359 | - "Certificate received: \n\n%s\n\n%s\n" | |
| 360 | - "Either:\n" | |
| 361 | - " * verify the certificate is correct using the " | |
| 362 | - "SHA1 fingerprint above\n" | |
| 363 | - " * use the global ssl-ca-location setting to specify your CA root\n" | |
| 364 | - " certificates list\n\n" | |
| 365 | - "If you are not expecting this message, answer no and " | |
| 366 | - "contact your server\nadministrator.\n\n" | |
| 367 | - "Accept certificate for host %s (a=always/y/N)? ", | |
| 368 | - X509_verify_cert_error_string(e), desc, warning, | |
| 369 | - pUrlData->useProxy?pUrlData->hostname:pUrlData->name); | |
| 370 | - BIO_free(mem); | |
| 371 | - | |
| 372 | - prompt_user(prompt, &ans); | |
| 373 | - free(prompt); | |
| 374 | - cReply = blob_str(&ans)[0]; | |
| 375 | - blob_reset(&ans); | |
| 376 | - if( cReply!='y' && cReply!='Y' && cReply!='a' && cReply!='A') { | |
| 377 | - X509_free(cert); | |
| 378 | - ssl_set_errmsg("SSL certificate declined"); | |
| 379 | - ssl_close(); | |
| 380 | - return 1; | |
| 381 | - } | |
| 382 | - if( cReply=='a' || cReply=='A') { | |
| 383 | - if ( trusted==0 ){ | |
| 384 | - prompt_user("\nSave this certificate as fully trusted (a=always/N)? ", | |
| 385 | - &ans); | |
| 386 | - cReply = blob_str(&ans)[0]; | |
| 387 | - trusted = ( cReply=='a' || cReply=='A' ); | |
| 388 | - blob_reset(&ans); | |
| 389 | - } | |
| 390 | - ssl_save_certificate(pUrlData, cert, trusted); | |
| 339 | + char zHash[32*2+1]; | |
| 340 | + unsigned int mdLength = (int)sizeof(md); | |
| 341 | + | |
| 342 | + memset(md, 0, sizeof(md)); | |
| 343 | + zHash[0] = 0; | |
| 344 | + if( X509_digest(cert, EVP_sha3_256(), md, &mdLength) ){ | |
| 345 | + int j; | |
| 346 | + for(j=0; j<mdLength && j*2+1<sizeof(zHash); ++j){ | |
| 347 | + zHash[j*2] = "0123456789abcdef"[md[j]>>4]; | |
| 348 | + zHash[j*2+1] = "0123456789abcdef"[md[j]&0xf]; | |
| 349 | + } | |
| 350 | + zHash[j*2] = 0; | |
| 351 | + } | |
| 352 | + | |
| 353 | + if( ssl_certificate_exception_exists(pUrlData, zHash) ){ | |
| 354 | + /* Ignore the failure because an exception exists */ | |
| 355 | + ssl_one_time_exception(pUrlData, zHash); | |
| 356 | + }else{ | |
| 357 | + /* Tell the user about the failure and ask what to do */ | |
| 358 | + mem = BIO_new(BIO_s_mem()); | |
| 359 | + BIO_puts(mem, " subject: "); | |
| 360 | + X509_NAME_print_ex(mem, X509_get_subject_name(cert), 0, XN_FLAG_ONELINE); | |
| 361 | + BIO_puts(mem, "\n issuer: "); | |
| 362 | + X509_NAME_print_ex(mem, X509_get_issuer_name(cert), 0, XN_FLAG_ONELINE); | |
| 363 | + BIO_printf(mem, "\n sha256: %s", zHash); | |
| 364 | + BIO_get_mem_data(mem, &desc); | |
| 365 | + | |
| 366 | + prompt = mprintf("Unable to verify SSL cert from %s\n%s\n" | |
| 367 | + "accept this cert and continue (y/N)? ", | |
| 368 | + pUrlData->name, desc); | |
| 369 | + BIO_free(mem); | |
| 370 | + | |
| 371 | + prompt_user(prompt, &ans); | |
| 372 | + free(prompt); | |
| 373 | + cReply = blob_str(&ans)[0]; | |
| 374 | + blob_reset(&ans); | |
| 375 | + if( cReply!='y' && cReply!='Y' ){ | |
| 376 | + X509_free(cert); | |
| 377 | + ssl_set_errmsg("SSL cert declined"); | |
| 378 | + ssl_close(); | |
| 379 | + return 1; | |
| 380 | + } | |
| 381 | + ssl_one_time_exception(pUrlData, zHash); | |
| 382 | + prompt_user("remember this exception (y/N)? ", &ans); | |
| 383 | + cReply = blob_str(&ans)[0]; | |
| 384 | + if( cReply=='y' || cReply=='Y') { | |
| 385 | + ssl_remember_certificate_exception(pUrlData, zHash); | |
| 386 | + } | |
| 387 | + blob_reset(&ans); | |
| 391 | 388 | } |
| 392 | 389 | } |
| 393 | 390 | |
| 394 | 391 | /* Set the Global.zIpAddr variable to the server we are talking to. |
| 395 | 392 | ** This is used to populate the ipaddr column of the rcvfrom table, |
| @@ -416,60 +413,53 @@ | ||
| 416 | 413 | X509_free(cert); |
| 417 | 414 | return 0; |
| 418 | 415 | } |
| 419 | 416 | |
| 420 | 417 | /* |
| 421 | -** Save certificate to global config. | |
| 422 | -*/ | |
| 423 | -void ssl_save_certificate(UrlData *pUrlData, X509 *cert, int trusted){ | |
| 424 | - BIO *mem; | |
| 425 | - char *zCert, *zHost; | |
| 426 | - | |
| 427 | - mem = BIO_new(BIO_s_mem()); | |
| 428 | - PEM_write_bio_X509(mem, cert); | |
| 429 | - BIO_write(mem, "", 1); /* nul-terminate mem buffer */ | |
| 430 | - BIO_get_mem_data(mem, &zCert); | |
| 431 | - zHost = mprintf("cert:%s", | |
| 432 | - pUrlData->useProxy ? pUrlData->hostname : pUrlData->name); | |
| 433 | - db_set(zHost, zCert, 1); | |
| 434 | - free(zHost); | |
| 435 | - zHost = mprintf("trusted:%s", | |
| 436 | - pUrlData->useProxy ? pUrlData->hostname : pUrlData->name); | |
| 437 | - db_set_int(zHost, trusted, 1); | |
| 438 | - free(zHost); | |
| 439 | - BIO_free(mem); | |
| 418 | +** Remember that the cert with the given hash is a acceptable for | |
| 419 | +** use with pUrlData->name. | |
| 420 | +*/ | |
| 421 | +LOCAL void ssl_remember_certificate_exception( | |
| 422 | + UrlData *pUrlData, | |
| 423 | + const char *zHash | |
| 424 | +){ | |
| 425 | + char *zName = mprintf("cert:%s", pUrlData->name); | |
| 426 | + db_set(zName, zHash, 1); | |
| 427 | + fossil_free(zName); | |
| 428 | +} | |
| 429 | + | |
| 430 | +/* | |
| 431 | +** Return true if the there exists a certificate exception for | |
| 432 | +** pUrlData->name that matches the hash. | |
| 433 | +*/ | |
| 434 | +LOCAL int ssl_certificate_exception_exists( | |
| 435 | + UrlData *pUrlData, | |
| 436 | + const char *zHash | |
| 437 | +){ | |
| 438 | + char *zName, *zValue; | |
| 439 | + if( fossil_strcmp(sException.zHost,pUrlData->name)==0 | |
| 440 | + && fossil_strcmp(sException.zHash,zHash)==0 | |
| 441 | + ){ | |
| 442 | + return 1; | |
| 443 | + } | |
| 444 | + zName = mprintf("cert:%s", pUrlData->name); | |
| 445 | + zValue = db_get(zName,0); | |
| 446 | + fossil_free(zName); | |
| 447 | + return zValue!=0 && strcmp(zHash,zValue)==0; | |
| 440 | 448 | } |
| 441 | 449 | |
| 442 | 450 | /* |
| 443 | -** Get certificate for pUrlData->urlName from global config. | |
| 444 | -** Return NULL if no certificate found. | |
| 451 | +** Remember zHash as an acceptable certificate for this session only. | |
| 445 | 452 | */ |
| 446 | -X509 *ssl_get_certificate(UrlData *pUrlData, int *pTrusted){ | |
| 447 | - char *zHost, *zCert; | |
| 448 | - BIO *mem; | |
| 449 | - X509 *cert; | |
| 450 | - | |
| 451 | - zHost = mprintf("cert:%s", | |
| 452 | - pUrlData->useProxy ? pUrlData->hostname : pUrlData->name); | |
| 453 | - zCert = db_get(zHost, NULL); | |
| 454 | - free(zHost); | |
| 455 | - if ( zCert==NULL ) | |
| 456 | - return NULL; | |
| 457 | - | |
| 458 | - if ( pTrusted!=0 ){ | |
| 459 | - zHost = mprintf("trusted:%s", | |
| 460 | - pUrlData->useProxy ? pUrlData->hostname : pUrlData->name); | |
| 461 | - *pTrusted = db_get_int(zHost, 0); | |
| 462 | - free(zHost); | |
| 463 | - } | |
| 464 | - | |
| 465 | - mem = BIO_new(BIO_s_mem()); | |
| 466 | - BIO_puts(mem, zCert); | |
| 467 | - cert = PEM_read_bio_X509(mem, NULL, 0, NULL); | |
| 468 | - free(zCert); | |
| 469 | - BIO_free(mem); | |
| 470 | - return cert; | |
| 453 | +LOCAL int ssl_one_time_exception( | |
| 454 | + UrlData *pUrlData, | |
| 455 | + const char *zHash | |
| 456 | +){ | |
| 457 | + fossil_free(sException.zHost); | |
| 458 | + sException.zHost = fossil_strdup(pUrlData->name); | |
| 459 | + fossil_free(sException.zHash); | |
| 460 | + sException.zHost = fossil_strdup(zHash); | |
| 471 | 461 | } |
| 472 | 462 | |
| 473 | 463 | /* |
| 474 | 464 | ** Send content out over the SSL connection. |
| 475 | 465 | */ |
| 476 | 466 |
| --- src/http_ssl.c | |
| +++ src/http_ssl.c | |
| @@ -46,11 +46,15 @@ | |
| 46 | static int sslIsInit = 0; /* True after global initialization */ |
| 47 | static BIO *iBio = 0; /* OpenSSL I/O abstraction */ |
| 48 | static char *sslErrMsg = 0; /* Text of most recent OpenSSL error */ |
| 49 | static SSL_CTX *sslCtx; /* SSL context */ |
| 50 | static SSL *ssl; |
| 51 | |
| 52 | |
| 53 | /* |
| 54 | ** Clear the SSL error message |
| 55 | */ |
| 56 | static void ssl_clear_errmsg(void){ |
| @@ -224,10 +228,21 @@ | |
| 224 | }while(!done); |
| 225 | sscanf(bbuf, "HTTP/1.%d %d", &httpVerMin, &rc); |
| 226 | blob_reset(&reply); |
| 227 | return rc; |
| 228 | } |
| 229 | |
| 230 | /* |
| 231 | ** Open an SSL connection. The identify of the server is determined |
| 232 | ** as follows: |
| 233 | ** |
| @@ -237,26 +252,13 @@ | |
| 237 | ** |
| 238 | ** Return the number of errors. |
| 239 | */ |
| 240 | int ssl_open(UrlData *pUrlData){ |
| 241 | X509 *cert; |
| 242 | int hasSavedCertificate = 0; |
| 243 | int trusted = 0; |
| 244 | unsigned long e; |
| 245 | |
| 246 | ssl_global_init(); |
| 247 | |
| 248 | /* Get certificate for current server from global config and |
| 249 | * (if we have it in config) add it to certificate store. |
| 250 | */ |
| 251 | cert = ssl_get_certificate(pUrlData, &trusted); |
| 252 | if ( cert!=NULL ){ |
| 253 | X509_STORE_add_cert(SSL_CTX_get_cert_store(sslCtx), cert); |
| 254 | X509_free(cert); |
| 255 | hasSavedCertificate = 1; |
| 256 | } |
| 257 | |
| 258 | if( pUrlData->useProxy ){ |
| 259 | int rc; |
| 260 | char *connStr = mprintf("%s:%d", g.url.name, pUrlData->port); |
| 261 | BIO *sBio = BIO_new_connect(connStr); |
| 262 | free(connStr); |
| @@ -326,70 +328,65 @@ | |
| 326 | ssl_set_errmsg("No SSL certificate was presented by the peer"); |
| 327 | ssl_close(); |
| 328 | return 1; |
| 329 | } |
| 330 | |
| 331 | if( trusted<=0 && (e = SSL_get_verify_result(ssl)) != X509_V_OK ){ |
| 332 | char *desc, *prompt; |
| 333 | const char *warning = ""; |
| 334 | Blob ans; |
| 335 | char cReply; |
| 336 | BIO *mem; |
| 337 | unsigned char md[32]; |
| 338 | unsigned int mdLength = 31; |
| 339 | |
| 340 | mem = BIO_new(BIO_s_mem()); |
| 341 | X509_NAME_print_ex(mem, X509_get_subject_name(cert), 2, XN_FLAG_MULTILINE); |
| 342 | BIO_puts(mem, "\n\nIssued By:\n\n"); |
| 343 | X509_NAME_print_ex(mem, X509_get_issuer_name(cert), 2, XN_FLAG_MULTILINE); |
| 344 | BIO_puts(mem, "\n\nSHA1 Fingerprint:\n\n "); |
| 345 | if(X509_digest(cert, EVP_sha1(), md, &mdLength)){ |
| 346 | int j; |
| 347 | for( j = 0; j < mdLength; ++j ) { |
| 348 | BIO_printf(mem, " %02x", md[j]); |
| 349 | } |
| 350 | } |
| 351 | BIO_write(mem, "", 1); /* nul-terminate mem buffer */ |
| 352 | BIO_get_mem_data(mem, &desc); |
| 353 | |
| 354 | if( hasSavedCertificate ){ |
| 355 | warning = "WARNING: Certificate doesn't match the " |
| 356 | "saved certificate for this host!"; |
| 357 | } |
| 358 | prompt = mprintf("\nSSL verification failed: %s\n" |
| 359 | "Certificate received: \n\n%s\n\n%s\n" |
| 360 | "Either:\n" |
| 361 | " * verify the certificate is correct using the " |
| 362 | "SHA1 fingerprint above\n" |
| 363 | " * use the global ssl-ca-location setting to specify your CA root\n" |
| 364 | " certificates list\n\n" |
| 365 | "If you are not expecting this message, answer no and " |
| 366 | "contact your server\nadministrator.\n\n" |
| 367 | "Accept certificate for host %s (a=always/y/N)? ", |
| 368 | X509_verify_cert_error_string(e), desc, warning, |
| 369 | pUrlData->useProxy?pUrlData->hostname:pUrlData->name); |
| 370 | BIO_free(mem); |
| 371 | |
| 372 | prompt_user(prompt, &ans); |
| 373 | free(prompt); |
| 374 | cReply = blob_str(&ans)[0]; |
| 375 | blob_reset(&ans); |
| 376 | if( cReply!='y' && cReply!='Y' && cReply!='a' && cReply!='A') { |
| 377 | X509_free(cert); |
| 378 | ssl_set_errmsg("SSL certificate declined"); |
| 379 | ssl_close(); |
| 380 | return 1; |
| 381 | } |
| 382 | if( cReply=='a' || cReply=='A') { |
| 383 | if ( trusted==0 ){ |
| 384 | prompt_user("\nSave this certificate as fully trusted (a=always/N)? ", |
| 385 | &ans); |
| 386 | cReply = blob_str(&ans)[0]; |
| 387 | trusted = ( cReply=='a' || cReply=='A' ); |
| 388 | blob_reset(&ans); |
| 389 | } |
| 390 | ssl_save_certificate(pUrlData, cert, trusted); |
| 391 | } |
| 392 | } |
| 393 | |
| 394 | /* Set the Global.zIpAddr variable to the server we are talking to. |
| 395 | ** This is used to populate the ipaddr column of the rcvfrom table, |
| @@ -416,60 +413,53 @@ | |
| 416 | X509_free(cert); |
| 417 | return 0; |
| 418 | } |
| 419 | |
| 420 | /* |
| 421 | ** Save certificate to global config. |
| 422 | */ |
| 423 | void ssl_save_certificate(UrlData *pUrlData, X509 *cert, int trusted){ |
| 424 | BIO *mem; |
| 425 | char *zCert, *zHost; |
| 426 | |
| 427 | mem = BIO_new(BIO_s_mem()); |
| 428 | PEM_write_bio_X509(mem, cert); |
| 429 | BIO_write(mem, "", 1); /* nul-terminate mem buffer */ |
| 430 | BIO_get_mem_data(mem, &zCert); |
| 431 | zHost = mprintf("cert:%s", |
| 432 | pUrlData->useProxy ? pUrlData->hostname : pUrlData->name); |
| 433 | db_set(zHost, zCert, 1); |
| 434 | free(zHost); |
| 435 | zHost = mprintf("trusted:%s", |
| 436 | pUrlData->useProxy ? pUrlData->hostname : pUrlData->name); |
| 437 | db_set_int(zHost, trusted, 1); |
| 438 | free(zHost); |
| 439 | BIO_free(mem); |
| 440 | } |
| 441 | |
| 442 | /* |
| 443 | ** Get certificate for pUrlData->urlName from global config. |
| 444 | ** Return NULL if no certificate found. |
| 445 | */ |
| 446 | X509 *ssl_get_certificate(UrlData *pUrlData, int *pTrusted){ |
| 447 | char *zHost, *zCert; |
| 448 | BIO *mem; |
| 449 | X509 *cert; |
| 450 | |
| 451 | zHost = mprintf("cert:%s", |
| 452 | pUrlData->useProxy ? pUrlData->hostname : pUrlData->name); |
| 453 | zCert = db_get(zHost, NULL); |
| 454 | free(zHost); |
| 455 | if ( zCert==NULL ) |
| 456 | return NULL; |
| 457 | |
| 458 | if ( pTrusted!=0 ){ |
| 459 | zHost = mprintf("trusted:%s", |
| 460 | pUrlData->useProxy ? pUrlData->hostname : pUrlData->name); |
| 461 | *pTrusted = db_get_int(zHost, 0); |
| 462 | free(zHost); |
| 463 | } |
| 464 | |
| 465 | mem = BIO_new(BIO_s_mem()); |
| 466 | BIO_puts(mem, zCert); |
| 467 | cert = PEM_read_bio_X509(mem, NULL, 0, NULL); |
| 468 | free(zCert); |
| 469 | BIO_free(mem); |
| 470 | return cert; |
| 471 | } |
| 472 | |
| 473 | /* |
| 474 | ** Send content out over the SSL connection. |
| 475 | */ |
| 476 |
| --- src/http_ssl.c | |
| +++ src/http_ssl.c | |
| @@ -46,11 +46,15 @@ | |
| 46 | static int sslIsInit = 0; /* True after global initialization */ |
| 47 | static BIO *iBio = 0; /* OpenSSL I/O abstraction */ |
| 48 | static char *sslErrMsg = 0; /* Text of most recent OpenSSL error */ |
| 49 | static SSL_CTX *sslCtx; /* SSL context */ |
| 50 | static SSL *ssl; |
| 51 | static struct { /* Accept this SSL cert for this session only */ |
| 52 | char *zHost; /* Subject or host name */ |
| 53 | char *zHash; /* SHA3 hash of the cert */ |
| 54 | } sException; |
| 55 | static int sslNoCertVerify = 0; /* Do not verify SSL certs */ |
| 56 | |
| 57 | /* |
| 58 | ** Clear the SSL error message |
| 59 | */ |
| 60 | static void ssl_clear_errmsg(void){ |
| @@ -224,10 +228,21 @@ | |
| 228 | }while(!done); |
| 229 | sscanf(bbuf, "HTTP/1.%d %d", &httpVerMin, &rc); |
| 230 | blob_reset(&reply); |
| 231 | return rc; |
| 232 | } |
| 233 | |
| 234 | /* |
| 235 | ** Invoke this routine to disable SSL cert verification. After |
| 236 | ** this call is made, any SSL cert that the server provides will |
| 237 | ** be accepted. Communication will still be encrypted, but the |
| 238 | ** client has no way of knowing whether it is talking to the |
| 239 | ** real server or a man-in-the-middle imposter. |
| 240 | */ |
| 241 | int ssl_disable_cert_verification(void){ |
| 242 | sslNoCertVerify = 1; |
| 243 | } |
| 244 | |
| 245 | /* |
| 246 | ** Open an SSL connection. The identify of the server is determined |
| 247 | ** as follows: |
| 248 | ** |
| @@ -237,26 +252,13 @@ | |
| 252 | ** |
| 253 | ** Return the number of errors. |
| 254 | */ |
| 255 | int ssl_open(UrlData *pUrlData){ |
| 256 | X509 *cert; |
| 257 | unsigned long e; |
| 258 | |
| 259 | ssl_global_init(); |
| 260 | if( pUrlData->useProxy ){ |
| 261 | int rc; |
| 262 | char *connStr = mprintf("%s:%d", g.url.name, pUrlData->port); |
| 263 | BIO *sBio = BIO_new_connect(connStr); |
| 264 | free(connStr); |
| @@ -326,70 +328,65 @@ | |
| 328 | ssl_set_errmsg("No SSL certificate was presented by the peer"); |
| 329 | ssl_close(); |
| 330 | return 1; |
| 331 | } |
| 332 | |
| 333 | if( !sslNoCertVerify && SSL_get_verify_result(ssl)!=X509_V_OK ){ |
| 334 | char *desc, *prompt; |
| 335 | Blob ans; |
| 336 | char cReply; |
| 337 | BIO *mem; |
| 338 | unsigned char md[32]; |
| 339 | char zHash[32*2+1]; |
| 340 | unsigned int mdLength = (int)sizeof(md); |
| 341 | |
| 342 | memset(md, 0, sizeof(md)); |
| 343 | zHash[0] = 0; |
| 344 | if( X509_digest(cert, EVP_sha3_256(), md, &mdLength) ){ |
| 345 | int j; |
| 346 | for(j=0; j<mdLength && j*2+1<sizeof(zHash); ++j){ |
| 347 | zHash[j*2] = "0123456789abcdef"[md[j]>>4]; |
| 348 | zHash[j*2+1] = "0123456789abcdef"[md[j]&0xf]; |
| 349 | } |
| 350 | zHash[j*2] = 0; |
| 351 | } |
| 352 | |
| 353 | if( ssl_certificate_exception_exists(pUrlData, zHash) ){ |
| 354 | /* Ignore the failure because an exception exists */ |
| 355 | ssl_one_time_exception(pUrlData, zHash); |
| 356 | }else{ |
| 357 | /* Tell the user about the failure and ask what to do */ |
| 358 | mem = BIO_new(BIO_s_mem()); |
| 359 | BIO_puts(mem, " subject: "); |
| 360 | X509_NAME_print_ex(mem, X509_get_subject_name(cert), 0, XN_FLAG_ONELINE); |
| 361 | BIO_puts(mem, "\n issuer: "); |
| 362 | X509_NAME_print_ex(mem, X509_get_issuer_name(cert), 0, XN_FLAG_ONELINE); |
| 363 | BIO_printf(mem, "\n sha256: %s", zHash); |
| 364 | BIO_get_mem_data(mem, &desc); |
| 365 | |
| 366 | prompt = mprintf("Unable to verify SSL cert from %s\n%s\n" |
| 367 | "accept this cert and continue (y/N)? ", |
| 368 | pUrlData->name, desc); |
| 369 | BIO_free(mem); |
| 370 | |
| 371 | prompt_user(prompt, &ans); |
| 372 | free(prompt); |
| 373 | cReply = blob_str(&ans)[0]; |
| 374 | blob_reset(&ans); |
| 375 | if( cReply!='y' && cReply!='Y' ){ |
| 376 | X509_free(cert); |
| 377 | ssl_set_errmsg("SSL cert declined"); |
| 378 | ssl_close(); |
| 379 | return 1; |
| 380 | } |
| 381 | ssl_one_time_exception(pUrlData, zHash); |
| 382 | prompt_user("remember this exception (y/N)? ", &ans); |
| 383 | cReply = blob_str(&ans)[0]; |
| 384 | if( cReply=='y' || cReply=='Y') { |
| 385 | ssl_remember_certificate_exception(pUrlData, zHash); |
| 386 | } |
| 387 | blob_reset(&ans); |
| 388 | } |
| 389 | } |
| 390 | |
| 391 | /* Set the Global.zIpAddr variable to the server we are talking to. |
| 392 | ** This is used to populate the ipaddr column of the rcvfrom table, |
| @@ -416,60 +413,53 @@ | |
| 413 | X509_free(cert); |
| 414 | return 0; |
| 415 | } |
| 416 | |
| 417 | /* |
| 418 | ** Remember that the cert with the given hash is a acceptable for |
| 419 | ** use with pUrlData->name. |
| 420 | */ |
| 421 | LOCAL void ssl_remember_certificate_exception( |
| 422 | UrlData *pUrlData, |
| 423 | const char *zHash |
| 424 | ){ |
| 425 | char *zName = mprintf("cert:%s", pUrlData->name); |
| 426 | db_set(zName, zHash, 1); |
| 427 | fossil_free(zName); |
| 428 | } |
| 429 | |
| 430 | /* |
| 431 | ** Return true if the there exists a certificate exception for |
| 432 | ** pUrlData->name that matches the hash. |
| 433 | */ |
| 434 | LOCAL int ssl_certificate_exception_exists( |
| 435 | UrlData *pUrlData, |
| 436 | const char *zHash |
| 437 | ){ |
| 438 | char *zName, *zValue; |
| 439 | if( fossil_strcmp(sException.zHost,pUrlData->name)==0 |
| 440 | && fossil_strcmp(sException.zHash,zHash)==0 |
| 441 | ){ |
| 442 | return 1; |
| 443 | } |
| 444 | zName = mprintf("cert:%s", pUrlData->name); |
| 445 | zValue = db_get(zName,0); |
| 446 | fossil_free(zName); |
| 447 | return zValue!=0 && strcmp(zHash,zValue)==0; |
| 448 | } |
| 449 | |
| 450 | /* |
| 451 | ** Remember zHash as an acceptable certificate for this session only. |
| 452 | */ |
| 453 | LOCAL int ssl_one_time_exception( |
| 454 | UrlData *pUrlData, |
| 455 | const char *zHash |
| 456 | ){ |
| 457 | fossil_free(sException.zHost); |
| 458 | sException.zHost = fossil_strdup(pUrlData->name); |
| 459 | fossil_free(sException.zHash); |
| 460 | sException.zHost = fossil_strdup(zHash); |
| 461 | } |
| 462 | |
| 463 | /* |
| 464 | ** Send content out over the SSL connection. |
| 465 | */ |
| 466 |