static void ssl_nss_verified_cb(PurpleCertificateVerificationStatus st, gpointer userdata) { PurpleSslConnection *gsc = (PurpleSslConnection *) userdata; if (st == PURPLE_CERTIFICATE_VALID) { /* Certificate valid? Good! Do the connection! */ gsc->connect_cb(gsc->connect_cb_data, gsc, PURPLE_INPUT_READ); } else { /* Otherwise, signal an error */ if(gsc->error_cb != NULL) gsc->error_cb(gsc, PURPLE_SSL_CERTIFICATE_INVALID, gsc->connect_cb_data); purple_ssl_close(gsc); } }
/* * ssl_openssl_handshake_cb */ static void ssl_openssl_handshake_cb(gpointer data, gint source, PurpleInputCondition cond) { PurpleSslConnection *gsc = (PurpleSslConnection *)data; PurpleSslOpensslData *openssl_data = PURPLE_SSL_OPENSSL_DATA(gsc); int ret, ret2; purple_debug_info("openssl", "Connecting to %s\n", gsc->host); /* * do the negotiation that sets up the SSL connection between * here and there. */ ret = SSL_connect(openssl_data->ssl); if (ret <= 0) { ret2 = SSL_get_error(openssl_data->ssl, ret); if (ret2 == SSL_ERROR_WANT_READ || ret2 == SSL_ERROR_WANT_WRITE) return; purple_debug_error("openssl", "SSL_connect failed: %d\n", ret2); if (gsc->error_cb != NULL) gsc->error_cb(gsc, PURPLE_SSL_HANDSHAKE_FAILED, gsc->connect_cb_data); purple_ssl_close(gsc); return; } purple_input_remove(openssl_data->handshake_handler); openssl_data->handshake_handler = 0; purple_debug_info("openssl", "Connected to %s\n", gsc->host); /* SSL connected now */ gsc->connect_cb(gsc->connect_cb_data, gsc, cond); }
static void purple_ssl_connect_cb(gpointer data, gint source, const gchar *error_message) { PurpleSslConnection *gsc; PurpleSslOps *ops; gsc = data; gsc->connect_data = NULL; if (source < 0) { if (gsc->error_cb != NULL) gsc->error_cb(gsc, PURPLE_SSL_CONNECT_FAILED, gsc->connect_cb_data); purple_ssl_close(gsc); return; } gsc->fd = source; ops = purple_ssl_get_ops(); ops->connectfunc(gsc); }
static void query_cert_result(gboolean trusted, void *userdata) { struct query_cert_userdata *ud = (struct query_cert_userdata*)userdata; PurpleSslConnection *gsc = (PurpleSslConnection *)ud->gsc; CFRelease(ud->certs); free(ud->hostname); if (PURPLE_SSL_CONNECTION_IS_VALID(gsc)) { if (!trusted) { if (gsc->error_cb != NULL) gsc->error_cb(gsc, PURPLE_SSL_CERTIFICATE_INVALID, gsc->connect_cb_data); purple_ssl_close(ud->gsc); } else { purple_debug_info("cdsa", "SSL_connect complete\n"); /* SSL connected now */ ud->gsc->connect_cb(ud->gsc->connect_cb_data, ud->gsc, ud->cond); } } free(ud); }
static void ssl_cdsa_create_context(gpointer data) { PurpleSslConnection *gsc = (PurpleSslConnection *)data; PurpleAccount *account = gsc->account; PurpleSslCDSAData *cdsa_data; OSStatus err; bool requireFS = purple_account_get_bool(account, "require_forward_secrecy", FALSE); /* * allocate some memory to store variables for the cdsa connection. * the memory comes zero'd from g_new0 so we don't need to null the * pointers held in this struct. */ cdsa_data = g_new0(PurpleSslCDSAData, 1); gsc->private_data = cdsa_data; connections = g_list_append(connections, gsc); /* * allocate a new SSLContextRef object */ err = SSLNewContext(false, &cdsa_data->ssl_ctx); if (err != noErr) { purple_debug_error("cdsa", "SSLNewContext failed\n"); if (gsc->error_cb != NULL) gsc->error_cb(gsc, PURPLE_SSL_HANDSHAKE_FAILED, gsc->connect_cb_data); purple_ssl_close(gsc); return; } /* * Set up our callbacks for reading/writing the file descriptor */ err = SSLSetIOFuncs(cdsa_data->ssl_ctx, SocketRead, SocketWrite); if (err != noErr) { purple_debug_error("cdsa", "SSLSetIOFuncs failed\n"); if (gsc->error_cb != NULL) gsc->error_cb(gsc, PURPLE_SSL_HANDSHAKE_FAILED, gsc->connect_cb_data); purple_ssl_close(gsc); return; } /* * Pass the connection information to the connection to be used by our callbacks */ err = SSLSetConnection(cdsa_data->ssl_ctx, (SSLConnectionRef)(intptr_t)gsc->fd); if (err != noErr) { purple_debug_error("cdsa", "SSLSetConnection failed: %d\n", err); if (gsc->error_cb != NULL) gsc->error_cb(gsc, PURPLE_SSL_HANDSHAKE_FAILED, gsc->connect_cb_data); purple_ssl_close(gsc); return; } size_t numCiphers = 0; err = SSLGetNumberEnabledCiphers(cdsa_data->ssl_ctx, &numCiphers); if (err != noErr) { purple_debug_error("cdsa", "SSLGetNumberEnabledCiphers failed: %d\n", err); if (gsc->error_cb != NULL) gsc->error_cb(gsc, PURPLE_SSL_HANDSHAKE_FAILED, gsc->connect_cb_data); purple_ssl_close(gsc); return; } SSLCipherSuite ciphers[numCiphers]; err = SSLGetEnabledCiphers(cdsa_data->ssl_ctx, ciphers, &numCiphers); if (err != noErr) { purple_debug_error("cdsa", "SSLGetSupportedCiphers failed: %d\n", err); if (gsc->error_cb != NULL) gsc->error_cb(gsc, PURPLE_SSL_HANDSHAKE_FAILED, gsc->connect_cb_data); purple_ssl_close(gsc); return; } SSLCipherSuite enabledCiphers[numCiphers]; size_t numEnabledCiphers = 0; int i; for (i = 0; i < numCiphers; i++) { if (ssl_cdsa_use_cipher(ciphers[i], requireFS)) { enabledCiphers[numEnabledCiphers] = ciphers[i]; numEnabledCiphers++; } } err = SSLSetEnabledCiphers(cdsa_data->ssl_ctx, enabledCiphers, numEnabledCiphers); if (err != noErr) { purple_debug_error("cdsa", "SSLSetEnabledCiphers failed: %d\n", err); if (gsc->error_cb != NULL) gsc->error_cb(gsc, PURPLE_SSL_HANDSHAKE_FAILED, gsc->connect_cb_data); purple_ssl_close(gsc); return; } if (purple_account_get_bool(account, PURPLE_SSL_CDSA_BUGGY_TLS_WORKAROUND, false)) { purple_debug_info("cdsa", "Explicitly disabling TLS 1.1 and above to try and work around buggy TLS stacks\n"); OSStatus protoErr; protoErr = SSLSetProtocolVersionEnabled(cdsa_data->ssl_ctx, kSSLProtocolAll, false); if (protoErr != noErr) { purple_debug_error("cdsa", "SSLSetProtocolVersionEnabled failed to disable protocols\n"); if (gsc->error_cb != NULL) gsc->error_cb(gsc, PURPLE_SSL_HANDSHAKE_FAILED, gsc->connect_cb_data); purple_ssl_close(gsc); return; } protoErr = SSLSetProtocolVersionEnabled(cdsa_data->ssl_ctx, kSSLProtocol3, true); protoErr = SSLSetProtocolVersionEnabled(cdsa_data->ssl_ctx, kTLSProtocol1, true); } if(gsc->host) { /* * Set the peer's domain name so CDSA can check the certificate's CN */ err = SSLSetPeerDomainName(cdsa_data->ssl_ctx, gsc->host, strlen(gsc->host)); if (err != noErr) { purple_debug_error("cdsa", "SSLSetPeerDomainName failed\n"); if (gsc->error_cb != NULL) gsc->error_cb(gsc, PURPLE_SSL_HANDSHAKE_FAILED, gsc->connect_cb_data); purple_ssl_close(gsc); return; } } /* * Disable verifying the certificate chain. * We have to do that manually later on! This is the only way to be able to continue with a connection, even though the user * had to manually accept the certificate. */ err = SSLSetEnableCertVerify(cdsa_data->ssl_ctx, false); if (err != noErr) { purple_debug_error("cdsa", "SSLSetEnableCertVerify failed\n"); /* error is not fatal */ } cdsa_data->handshake_handler = purple_input_add(gsc->fd, PURPLE_INPUT_READ, ssl_cdsa_handshake_cb, gsc); }
/* * ssl_cdsa_handshake_cb */ static void ssl_cdsa_handshake_cb(gpointer data, gint source, PurpleInputCondition cond) { PurpleSslConnection *gsc = (PurpleSslConnection *)data; PurpleAccount *account = gsc->account; PurpleSslCDSAData *cdsa_data = PURPLE_SSL_CDSA_DATA(gsc); OSStatus err; purple_debug_info("cdsa", "Connecting\n"); /* * do the negotiation that sets up the SSL connection between * here and there. */ err = SSLHandshake(cdsa_data->ssl_ctx); if (err == errSSLPeerBadRecordMac && !purple_account_get_bool(account, PURPLE_SSL_CDSA_BUGGY_TLS_WORKAROUND, false) && !strcmp(purple_account_get_protocol_id(account),"prpl-jabber")) { /* * Set a flag so we know to explicitly disable TLS 1.1 and 1.2 on our next (immediate) connection attempt for this account. * Some XMPP servers use buggy TLS stacks that incorrectly report their capabilities, which breaks things with 10.8's new support * for TLS 1.1 and 1.2. */ purple_debug_info("cdsa", "SSLHandshake reported that the server rejected our MAC, which most likely means it lied about the TLS versions it supports."); purple_debug_info("cdsa", "Setting a flag in this account to only use TLS 1.0 and below on the next connection attempt."); purple_account_set_bool(account, PURPLE_SSL_CDSA_BUGGY_TLS_WORKAROUND, true); if (gsc->error_cb != NULL) gsc->error_cb(gsc, PURPLE_SSL_HANDSHAKE_FAILED, gsc->connect_cb_data); purple_ssl_close(gsc); return; } else if (err != noErr) { if(err == errSSLWouldBlock) return; fprintf(stderr,"cdsa: SSLHandshake failed with error %d\n",(int)err); purple_debug_error("cdsa", "SSLHandshake failed with error %d\n",(int)err); if (gsc->error_cb != NULL) gsc->error_cb(gsc, PURPLE_SSL_HANDSHAKE_FAILED, gsc->connect_cb_data); purple_ssl_close(gsc); return; } SSLCipherSuite cipher; SSLProtocol protocol; err = SSLGetNegotiatedCipher(cdsa_data->ssl_ctx, &cipher); if (err == noErr) { err = SSLGetNegotiatedProtocolVersion(cdsa_data->ssl_ctx, &protocol); purple_debug_info("cdsa", "Your connection is using %s with %s encryption, using %s for message authentication and %s key exchange (%X).\n", SSLVersionToString(protocol), SSLCipherName(cipher), SSLMACName(cipher), SSLKeyExchangeName(cipher), cipher); } purple_input_remove(cdsa_data->handshake_handler); cdsa_data->handshake_handler = 0; purple_debug_info("cdsa", "SSL_connect: verifying certificate\n"); if(certificate_ui_cb) { // does the application want to verify the certificate? struct query_cert_userdata *userdata = (struct query_cert_userdata*)malloc(sizeof(struct query_cert_userdata)); size_t hostnamelen = 0; SSLGetPeerDomainNameLength(cdsa_data->ssl_ctx, &hostnamelen); userdata->hostname = (char*)malloc(hostnamelen+1); SSLGetPeerDomainName(cdsa_data->ssl_ctx, userdata->hostname, &hostnamelen); userdata->hostname[hostnamelen] = '\0'; // just make sure it's zero-terminated userdata->cond = cond; userdata->gsc = gsc; SSLCopyPeerCertificates(cdsa_data->ssl_ctx, &userdata->certs); certificate_ui_cb(gsc, userdata->hostname, userdata->certs, query_cert_result, userdata); } else { purple_debug_info("cdsa", "SSL_connect complete (did not verify certificate)\n"); /* SSL connected now */ gsc->connect_cb(gsc->connect_cb_data, gsc, cond); } SSLCipherSuite suite; SSLGetNegotiatedCipher(cdsa_data->ssl_ctx, &suite); purple_debug_info("cdsa", "Using cipher %x.\n", suite); }
static void ssl_gnutls_handshake_cb(gpointer data, gint source, PurpleInputCondition cond) { PurpleSslConnection *gsc = data; PurpleSslGnutlsData *gnutls_data = PURPLE_SSL_GNUTLS_DATA(gsc); ssize_t ret; /*purple_debug_info("gnutls", "Handshaking with %s\n", gsc->host);*/ ret = gnutls_handshake(gnutls_data->session); if(ret == GNUTLS_E_AGAIN || ret == GNUTLS_E_INTERRUPTED) return; purple_input_remove(gnutls_data->handshake_handler); gnutls_data->handshake_handler = 0; if(ret != 0) { purple_debug_error("gnutls", "Handshake failed. Error %s\n", gnutls_strerror(ret)); if(gsc->error_cb != NULL) gsc->error_cb(gsc, PURPLE_SSL_HANDSHAKE_FAILED, gsc->connect_cb_data); purple_ssl_close(gsc); } else { /* Now we are cooking with gas! */ PurpleSslOps *ops = purple_ssl_get_ops(); GList * peers = ops->get_peer_certificates(gsc); PurpleCertificateScheme *x509 = purple_certificate_find_scheme("x509"); GList * l; /* TODO: Remove all this debugging babble */ purple_debug_info("gnutls", "Handshake complete\n"); for (l=peers; l; l = l->next) { PurpleCertificate *crt = l->data; GByteArray *z = x509->get_fingerprint_sha1(crt); gchar * fpr = purple_base16_encode_chunked(z->data, z->len); purple_debug_info("gnutls/x509", "Key print: %s\n", fpr); /* Kill the cert! */ x509->destroy_certificate(crt); g_free(fpr); g_byte_array_free(z, TRUE); } g_list_free(peers); { const gnutls_datum_t *cert_list; unsigned int cert_list_size = 0; gnutls_session_t session=gnutls_data->session; guint i; cert_list = gnutls_certificate_get_peers(session, &cert_list_size); purple_debug_info("gnutls", "Peer provided %d certs\n", cert_list_size); for (i=0; i<cert_list_size; i++) { gchar fpr_bin[256]; gsize fpr_bin_sz = sizeof(fpr_bin); gchar * fpr_asc = NULL; gchar tbuf[256]; gsize tsz=sizeof(tbuf); gchar * tasc = NULL; gnutls_x509_crt_t cert; gnutls_x509_crt_init(&cert); gnutls_x509_crt_import (cert, &cert_list[i], GNUTLS_X509_FMT_DER); gnutls_x509_crt_get_fingerprint(cert, GNUTLS_DIG_SHA, fpr_bin, &fpr_bin_sz); fpr_asc = purple_base16_encode_chunked((const guchar *)fpr_bin, fpr_bin_sz); purple_debug_info("gnutls", "Lvl %d SHA1 fingerprint: %s\n", i, fpr_asc); tsz=sizeof(tbuf); gnutls_x509_crt_get_serial(cert,tbuf,&tsz); tasc=purple_base16_encode_chunked((const guchar *)tbuf, tsz); purple_debug_info("gnutls", "Serial: %s\n", tasc); g_free(tasc); tsz=sizeof(tbuf); gnutls_x509_crt_get_dn (cert, tbuf, &tsz); purple_debug_info("gnutls", "Cert DN: %s\n", tbuf); tsz=sizeof(tbuf); gnutls_x509_crt_get_issuer_dn (cert, tbuf, &tsz); purple_debug_info("gnutls", "Cert Issuer DN: %s\n", tbuf); g_free(fpr_asc); fpr_asc = NULL; gnutls_x509_crt_deinit(cert); } } /* TODO: The following logic should really be in libpurple */ /* If a Verifier was given, hand control over to it */ if (gsc->verifier) { GList *peers; /* First, get the peer cert chain */ peers = purple_ssl_get_peer_certificates(gsc); /* Now kick off the verification process */ purple_certificate_verify(gsc->verifier, gsc->host, peers, ssl_gnutls_verified_cb, gsc); purple_certificate_destroy_list(peers); } else { /* Otherwise, just call the "connection complete" callback */ gsc->connect_cb(gsc->connect_cb_data, gsc, cond); } } }