OpenSSL keychain certificate verification broken with callbacks

Originator:bruno
Number:rdar://10509597 Date Originated:01-Dec-2011 10:03 AM
Status:Open Resolved:
Product:Mac OS X Product Version:10.7.2
Classification:Other Bug Reproducible:Always
 
Summary:

Since Snow Leopard, Apple patched the OpenSSL library that ships with the OS to look for trusted root certificates in the Mac OS X keychain. Essentially, the patch is here: http://opensource.apple.com/source/OpenSSL098/OpenSSL098-27/src/crypto/x509/x509_vfy_apple.c

The patch correctly affects the output of SSL_get_verify_result (see "Test 0" below).
However, the patched OpenSSL library operates incorrectly with respect to the "verify callback" functionality of OpenSSL (functions SSL_CTX_set_verify and SSL_set_verify); during the certificate verification, registered callback functions are called with wrong verification results (see "Test 1").

The problem originates from the fact that the patch is implemented as a wrapper to the original X509_verify_cert function in OpenSSL, so it comes "too late" to influence the callback mechanism.

The situation is even worse when a callback indicates to continue the certificate verification process in case of an error. In that case, the verify result may change into something other than the single verify result that the patch picks up (X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY), causing the patch not to work at all and causing the verification to fail entirely (see "Test 2" below).

Steps to Reproduce:

The included ssl-client.c is a minimal program that makes an SSL connection to the https service of www.apple.com. It does basic certificate verification ("Test 0"), verification with a callback that does not indicate to continue in case of errors ("Test 1"), and verification with a callback that does indicate to continue ("Test 2").

1. Compile ssl-client.c using the following command:
gcc -o ssl-client ssl-client.c -lssl -lcrypto
2. Verify that no trusted root certificates are installed in
/System/Library/OpenSSL/cert.pem
or
/System/Library/OpenSSL/certs/
3. Run ssl-client by tying:
./ssl-client

Expected Results:

Test 0: No callback
Verify OK
Test 1: Callback without continue
Preverify call: ok, depth 2, err 0
Preverify call: ok, depth 1, err 0
Preverify call: ok, depth 0, err 0
Verify OK
Test 2: Callback with continue
Preverify call: ok, depth 2, err 0
Preverify call: ok, depth 1, err 0
Preverify call: ok, depth 0, err 0
Verify OK

Actual Results:

Test 0: No callback
Verify OK
Test 1: Callback without continue
Preverify call: err, depth 1, err 20
Verify OK
Test 2: Callback with continue
Preverify call: err, depth 1, err 20
Preverify call: err, depth 1, err 27
Preverify call: ok, depth 0, err 27
Verify failed

Regression:

This problem can be equally reproduced on both Snow Leopard (10.6.8) and Lion (10.7.2).

Notes:

This problem affects other open source software that ships with Mac OS X. Please see the following reports on the web.

http://redmine.ruby-lang.org/issues/3150
http://permalink.gmane.org/gmane.comp.web.webdav.neon.general/815

It is very important to note that on Mac OS X Lion, the version of Neon that is included (0.29.0), employs OpenSSL callbacks and therefore fails to verify any certificates. This greatly affect the usability of the included Subversion, which employs Neon for https connection. (Back on Mac OS X Snow Leopard, the included version of Neon (0.28.6) did not use callbacks yet.)

Comments

ssl-client.c

include

include

include

include

include

include

include

include

include

include

include

int cont = 0;

int verify_callback(int preverify_ok, X509_STORE_CTX *x509_ctx) { int depth = X509_STORE_CTX_get_error_depth(x509_ctx); int err = X509_STORE_CTX_get_error(x509_ctx); fprintf(stderr, "Preverify call: %s, depth %d, err %dn", preverify_ok == 0 ? "err" : "ok", depth, err); return cont ? 1 : preverify_ok; }

void ssl_test(struct addrinfo ai, SSL_CTX ctx) { int r;

// Create socket
int sock = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);

// Open connection
r = connect(sock, ai->ai_addr, ai->ai_addrlen);
if(r == -1) {
    fprintf(stderr, "Could not connect: %s\n", strerror(errno));
    close(sock);
    return;
}

// Create SSL
SSL *ssl = SSL_new(ctx);
SSL_set_fd(ssl, sock);

// Start SSL connection
r = SSL_connect(ssl);
if(r != 1) {
    r = SSL_get_error(ssl, r);
    fprintf(stderr, "SSL error: %d\n", r);
    SSL_free(ssl);
    close(sock);
    return;
}

long vfy = SSL_get_verify_result(ssl);
fprintf(stderr, vfy == X509_V_OK ? "Verify OK\n" : "Verify failed\n");

// Shutdown ssl
SSL_shutdown(ssl);
// Free ssl
SSL_free(ssl);
// Close connection
close(sock);

}

int main() { // Init SSL_load_error_strings(); SSL_library_init();

// Lookup address info
struct addrinfo *ai;
{
    struct addrinfo hints;
    memset(&hints, 0, sizeof(hints));
    hints.ai_socktype = SOCK_STREAM;
    if(getaddrinfo("www.apple.com", "https", &hints, &ai)) {
        fprintf(stderr, "Name lookup error\n");
        return 1;
    }
}

// Create context
SSL_CTX *ctx = SSL_CTX_new(SSLv23_client_method());
SSL_CTX_set_default_verify_paths(ctx);

// Execute tests
fprintf(stderr, "Test 0: No callback\n");
ssl_test(ai, ctx);
fprintf(stderr, "Test 1: Callback without continue\n");
cont = 0;
SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, &verify_callback);
ssl_test(ai, ctx);
fprintf(stderr, "Test 2: Callback with continue\n");
cont = 1;
ssl_test(ai, ctx);

// Free context
SSL_CTX_free(ctx);

// Free address info
freeaddrinfo(ai);

return 0;

}


Please note: Reports posted here will not necessarily be seen by Apple. All problems should be submitted at bugreport.apple.com before they are posted here. Please only post information for Radars that you have filed yourself, and please do not include Apple confidential information in your posts. Thank you!