| /* |
| * SSL/TLS interface functions for NSS |
| * Copyright (c) 2009, Jouni Malinen <j@w1.fi> |
| * |
| * This software may be distributed under the terms of the BSD license. |
| * See README for more details. |
| */ |
| |
| #include "includes.h" |
| #include <nspr/prtypes.h> |
| #include <nspr/plarenas.h> |
| #include <nspr/plhash.h> |
| #include <nspr/prio.h> |
| #include <nspr/prclist.h> |
| #include <nspr/prlock.h> |
| #include <nspr/prinit.h> |
| #include <nspr/prerror.h> |
| #include <nspr/prmem.h> |
| #include <nss/nss.h> |
| #include <nss/nssilckt.h> |
| #include <nss/ssl.h> |
| #include <nss/pk11func.h> |
| #include <nss/secerr.h> |
| |
| #include "common.h" |
| #include "tls.h" |
| |
| static int tls_nss_ref_count = 0; |
| |
| static PRDescIdentity nss_layer_id; |
| |
| |
| struct tls_connection { |
| PRFileDesc *fd; |
| |
| int established; |
| int verify_peer; |
| u8 *push_buf, *pull_buf, *pull_buf_offset; |
| size_t push_buf_len, pull_buf_len; |
| }; |
| |
| |
| static PRStatus nss_io_close(PRFileDesc *fd) |
| { |
| wpa_printf(MSG_DEBUG, "NSS: I/O close"); |
| return PR_SUCCESS; |
| } |
| |
| |
| static PRInt32 nss_io_read(PRFileDesc *fd, void *buf, PRInt32 amount) |
| { |
| wpa_printf(MSG_DEBUG, "NSS: I/O read(%d)", amount); |
| return PR_FAILURE; |
| } |
| |
| |
| static PRInt32 nss_io_write(PRFileDesc *fd, const void *buf, PRInt32 amount) |
| { |
| wpa_printf(MSG_DEBUG, "NSS: I/O write(%d)", amount); |
| return PR_FAILURE; |
| } |
| |
| |
| static PRInt32 nss_io_writev(PRFileDesc *fd, const PRIOVec *iov, |
| PRInt32 iov_size, PRIntervalTime timeout) |
| { |
| wpa_printf(MSG_DEBUG, "NSS: I/O writev(%d)", iov_size); |
| return PR_FAILURE; |
| } |
| |
| |
| static PRInt32 nss_io_recv(PRFileDesc *fd, void *buf, PRInt32 amount, |
| PRIntn flags, PRIntervalTime timeout) |
| { |
| struct tls_connection *conn = (struct tls_connection *) fd->secret; |
| u8 *end; |
| |
| wpa_printf(MSG_DEBUG, "NSS: I/O recv(%d)", amount); |
| |
| if (conn->pull_buf == NULL) { |
| wpa_printf(MSG_DEBUG, "NSS: No data available to be read yet"); |
| return PR_FAILURE; |
| } |
| |
| end = conn->pull_buf + conn->pull_buf_len; |
| if (end - conn->pull_buf_offset < amount) |
| amount = end - conn->pull_buf_offset; |
| os_memcpy(buf, conn->pull_buf_offset, amount); |
| conn->pull_buf_offset += amount; |
| if (conn->pull_buf_offset == end) { |
| wpa_printf(MSG_DEBUG, "%s - pull_buf consumed", __func__); |
| os_free(conn->pull_buf); |
| conn->pull_buf = conn->pull_buf_offset = NULL; |
| conn->pull_buf_len = 0; |
| } else { |
| wpa_printf(MSG_DEBUG, "%s - %lu bytes remaining in pull_buf", |
| __func__, |
| (unsigned long) (end - conn->pull_buf_offset)); |
| } |
| return amount; |
| } |
| |
| |
| static PRInt32 nss_io_send(PRFileDesc *fd, const void *buf, PRInt32 amount, |
| PRIntn flags, PRIntervalTime timeout) |
| { |
| struct tls_connection *conn = (struct tls_connection *) fd->secret; |
| u8 *nbuf; |
| |
| wpa_printf(MSG_DEBUG, "NSS: I/O %s", __func__); |
| wpa_hexdump(MSG_MSGDUMP, "NSS: I/O send data", buf, amount); |
| |
| nbuf = os_realloc(conn->push_buf, conn->push_buf_len + amount); |
| if (nbuf == NULL) { |
| wpa_printf(MSG_ERROR, "NSS: Failed to allocate memory for the " |
| "data to be sent"); |
| return PR_FAILURE; |
| } |
| os_memcpy(nbuf + conn->push_buf_len, buf, amount); |
| conn->push_buf = nbuf; |
| conn->push_buf_len += amount; |
| |
| return amount; |
| } |
| |
| |
| static PRInt32 nss_io_recvfrom(PRFileDesc *fd, void *buf, PRInt32 amount, |
| PRIntn flags, PRNetAddr *addr, |
| PRIntervalTime timeout) |
| { |
| wpa_printf(MSG_DEBUG, "NSS: I/O %s", __func__); |
| return PR_FAILURE; |
| } |
| |
| |
| static PRInt32 nss_io_sendto(PRFileDesc *fd, const void *buf, PRInt32 amount, |
| PRIntn flags, const PRNetAddr *addr, |
| PRIntervalTime timeout) |
| { |
| wpa_printf(MSG_DEBUG, "NSS: I/O %s", __func__); |
| return PR_FAILURE; |
| } |
| |
| |
| static PRStatus nss_io_getpeername(PRFileDesc *fd, PRNetAddr *addr) |
| { |
| wpa_printf(MSG_DEBUG, "NSS: I/O getpeername"); |
| |
| /* |
| * It Looks like NSS only supports IPv4 and IPv6 TCP sockets. Provide a |
| * fake IPv4 address to work around this even though we are not really |
| * using TCP. |
| */ |
| os_memset(addr, 0, sizeof(*addr)); |
| addr->inet.family = PR_AF_INET; |
| |
| return PR_SUCCESS; |
| } |
| |
| |
| static PRStatus nss_io_getsocketoption(PRFileDesc *fd, |
| PRSocketOptionData *data) |
| { |
| switch (data->option) { |
| case PR_SockOpt_Nonblocking: |
| wpa_printf(MSG_DEBUG, "NSS: I/O getsocketoption(Nonblocking)"); |
| data->value.non_blocking = PR_TRUE; |
| return PR_SUCCESS; |
| default: |
| wpa_printf(MSG_DEBUG, "NSS: I/O getsocketoption(%d)", |
| data->option); |
| return PR_FAILURE; |
| } |
| } |
| |
| |
| static const PRIOMethods nss_io = { |
| PR_DESC_LAYERED, |
| nss_io_close, |
| nss_io_read, |
| nss_io_write, |
| NULL /* available */, |
| NULL /* available64 */, |
| NULL /* fsync */, |
| NULL /* fseek */, |
| NULL /* fseek64 */, |
| NULL /* fileinfo */, |
| NULL /* fileinfo64 */, |
| nss_io_writev, |
| NULL /* connect */, |
| NULL /* accept */, |
| NULL /* bind */, |
| NULL /* listen */, |
| NULL /* shutdown */, |
| nss_io_recv, |
| nss_io_send, |
| nss_io_recvfrom, |
| nss_io_sendto, |
| NULL /* poll */, |
| NULL /* acceptread */, |
| NULL /* transmitfile */, |
| NULL /* getsockname */, |
| nss_io_getpeername, |
| NULL /* reserved_fn_6 */, |
| NULL /* reserved_fn_5 */, |
| nss_io_getsocketoption, |
| NULL /* setsocketoption */, |
| NULL /* sendfile */, |
| NULL /* connectcontinue */, |
| NULL /* reserved_fn_3 */, |
| NULL /* reserved_fn_2 */, |
| NULL /* reserved_fn_1 */, |
| NULL /* reserved_fn_0 */ |
| }; |
| |
| |
| static char * nss_password_cb(PK11SlotInfo *slot, PRBool retry, void *arg) |
| { |
| wpa_printf(MSG_ERROR, "NSS: TODO - %s", __func__); |
| return NULL; |
| } |
| |
| |
| void * tls_init(const struct tls_config *conf) |
| { |
| char *dir; |
| |
| tls_nss_ref_count++; |
| if (tls_nss_ref_count > 1) |
| return (void *) 1; |
| |
| PR_Init(PR_SYSTEM_THREAD, PR_PRIORITY_NORMAL, 1); |
| |
| nss_layer_id = PR_GetUniqueIdentity("wpa_supplicant"); |
| |
| PK11_SetPasswordFunc(nss_password_cb); |
| |
| dir = getenv("SSL_DIR"); |
| if (dir) { |
| if (NSS_Init(dir) != SECSuccess) { |
| wpa_printf(MSG_ERROR, "NSS: NSS_Init(cert_dir=%s) " |
| "failed", dir); |
| return NULL; |
| } |
| } else { |
| if (NSS_NoDB_Init(NULL) != SECSuccess) { |
| wpa_printf(MSG_ERROR, "NSS: NSS_NoDB_Init(NULL) " |
| "failed"); |
| return NULL; |
| } |
| } |
| |
| if (SSL_OptionSetDefault(SSL_V2_COMPATIBLE_HELLO, PR_FALSE) != |
| SECSuccess || |
| SSL_OptionSetDefault(SSL_ENABLE_SSL3, PR_FALSE) != SECSuccess || |
| SSL_OptionSetDefault(SSL_ENABLE_SSL2, PR_FALSE) != SECSuccess || |
| SSL_OptionSetDefault(SSL_ENABLE_TLS, PR_TRUE) != SECSuccess) { |
| wpa_printf(MSG_ERROR, "NSS: SSL_OptionSetDefault failed"); |
| return NULL; |
| } |
| |
| if (NSS_SetDomesticPolicy() != SECSuccess) { |
| wpa_printf(MSG_ERROR, "NSS: NSS_SetDomesticPolicy() failed"); |
| return NULL; |
| } |
| |
| return (void *) 1; |
| } |
| |
| void tls_deinit(void *ssl_ctx) |
| { |
| tls_nss_ref_count--; |
| if (tls_nss_ref_count == 0) { |
| if (NSS_Shutdown() != SECSuccess) |
| wpa_printf(MSG_ERROR, "NSS: NSS_Shutdown() failed"); |
| } |
| } |
| |
| |
| int tls_get_errors(void *tls_ctx) |
| { |
| return 0; |
| } |
| |
| |
| static SECStatus nss_bad_cert_cb(void *arg, PRFileDesc *fd) |
| { |
| struct tls_connection *conn = arg; |
| SECStatus res = SECSuccess; |
| PRErrorCode err; |
| CERTCertificate *cert; |
| char *subject, *issuer; |
| |
| err = PR_GetError(); |
| if (IS_SEC_ERROR(err)) |
| wpa_printf(MSG_DEBUG, "NSS: Bad Server Certificate (sec err " |
| "%d)", err - SEC_ERROR_BASE); |
| else |
| wpa_printf(MSG_DEBUG, "NSS: Bad Server Certificate (err %d)", |
| err); |
| cert = SSL_PeerCertificate(fd); |
| subject = CERT_NameToAscii(&cert->subject); |
| issuer = CERT_NameToAscii(&cert->issuer); |
| wpa_printf(MSG_DEBUG, "NSS: Peer certificate subject='%s' issuer='%s'", |
| subject, issuer); |
| CERT_DestroyCertificate(cert); |
| PR_Free(subject); |
| PR_Free(issuer); |
| if (conn->verify_peer) |
| res = SECFailure; |
| |
| return res; |
| } |
| |
| |
| static void nss_handshake_cb(PRFileDesc *fd, void *client_data) |
| { |
| struct tls_connection *conn = client_data; |
| wpa_printf(MSG_DEBUG, "NSS: Handshake completed"); |
| conn->established = 1; |
| } |
| |
| |
| struct tls_connection * tls_connection_init(void *tls_ctx) |
| { |
| struct tls_connection *conn; |
| |
| conn = os_zalloc(sizeof(*conn)); |
| if (conn == NULL) |
| return NULL; |
| |
| conn->fd = PR_CreateIOLayerStub(nss_layer_id, &nss_io); |
| if (conn->fd == NULL) { |
| os_free(conn); |
| return NULL; |
| } |
| conn->fd->secret = (void *) conn; |
| |
| conn->fd = SSL_ImportFD(NULL, conn->fd); |
| if (conn->fd == NULL) { |
| os_free(conn); |
| return NULL; |
| } |
| |
| if (SSL_OptionSet(conn->fd, SSL_SECURITY, PR_TRUE) != SECSuccess || |
| SSL_OptionSet(conn->fd, SSL_HANDSHAKE_AS_CLIENT, PR_TRUE) != |
| SECSuccess || |
| SSL_OptionSet(conn->fd, SSL_HANDSHAKE_AS_SERVER, PR_FALSE) != |
| SECSuccess || |
| SSL_OptionSet(conn->fd, SSL_ENABLE_TLS, PR_TRUE) != SECSuccess || |
| SSL_BadCertHook(conn->fd, nss_bad_cert_cb, conn) != SECSuccess || |
| SSL_HandshakeCallback(conn->fd, nss_handshake_cb, conn) != |
| SECSuccess) { |
| wpa_printf(MSG_ERROR, "NSS: Failed to set options"); |
| PR_Close(conn->fd); |
| os_free(conn); |
| return NULL; |
| } |
| |
| SSL_ResetHandshake(conn->fd, PR_FALSE); |
| |
| return conn; |
| } |
| |
| |
| void tls_connection_deinit(void *tls_ctx, struct tls_connection *conn) |
| { |
| PR_Close(conn->fd); |
| os_free(conn->push_buf); |
| os_free(conn->pull_buf); |
| os_free(conn); |
| } |
| |
| |
| int tls_connection_established(void *tls_ctx, struct tls_connection *conn) |
| { |
| return conn->established; |
| } |
| |
| |
| int tls_connection_shutdown(void *tls_ctx, struct tls_connection *conn) |
| { |
| return -1; |
| } |
| |
| |
| int tls_connection_set_params(void *tls_ctx, struct tls_connection *conn, |
| const struct tls_connection_params *params) |
| { |
| wpa_printf(MSG_ERROR, "NSS: TODO - %s", __func__); |
| return 0; |
| } |
| |
| |
| int tls_global_set_params(void *tls_ctx, |
| const struct tls_connection_params *params) |
| { |
| return -1; |
| } |
| |
| |
| int tls_global_set_verify(void *tls_ctx, int check_crl) |
| { |
| return -1; |
| } |
| |
| |
| int tls_connection_set_verify(void *tls_ctx, struct tls_connection *conn, |
| int verify_peer) |
| { |
| conn->verify_peer = verify_peer; |
| return 0; |
| } |
| |
| |
| int tls_connection_get_keys(void *tls_ctx, struct tls_connection *conn, |
| struct tls_keys *keys) |
| { |
| /* NSS does not export master secret or client/server random. */ |
| return -1; |
| } |
| |
| |
| int tls_connection_prf(void *tls_ctx, struct tls_connection *conn, |
| const char *label, int server_random_first, |
| u8 *out, size_t out_len) |
| { |
| if (conn == NULL || server_random_first) { |
| wpa_printf(MSG_INFO, "NSS: Unsupported PRF request " |
| "(server_random_first=%d)", |
| server_random_first); |
| return -1; |
| } |
| |
| if (SSL_ExportKeyingMaterial(conn->fd, label, NULL, 0, out, out_len) != |
| SECSuccess) { |
| wpa_printf(MSG_INFO, "NSS: Failed to use TLS extractor " |
| "(label='%s' out_len=%d", label, (int) out_len); |
| return -1; |
| } |
| |
| return 0; |
| } |
| |
| |
| struct wpabuf * tls_connection_handshake(void *tls_ctx, |
| struct tls_connection *conn, |
| const struct wpabuf *in_data, |
| struct wpabuf **appl_data) |
| { |
| struct wpabuf *out_data; |
| |
| wpa_printf(MSG_DEBUG, "NSS: handshake: in_len=%u", |
| in_data ? (unsigned int) wpabuf_len(in_data) : 0); |
| |
| if (appl_data) |
| *appl_data = NULL; |
| |
| if (in_data && wpabuf_len(in_data) > 0) { |
| if (conn->pull_buf) { |
| wpa_printf(MSG_DEBUG, "%s - %lu bytes remaining in " |
| "pull_buf", __func__, |
| (unsigned long) conn->pull_buf_len); |
| os_free(conn->pull_buf); |
| } |
| conn->pull_buf = os_malloc(wpabuf_len(in_data)); |
| if (conn->pull_buf == NULL) |
| return NULL; |
| os_memcpy(conn->pull_buf, wpabuf_head(in_data), |
| wpabuf_len(in_data)); |
| conn->pull_buf_offset = conn->pull_buf; |
| conn->pull_buf_len = wpabuf_len(in_data); |
| } |
| |
| SSL_ForceHandshake(conn->fd); |
| |
| if (conn->established && conn->push_buf == NULL) { |
| /* Need to return something to get final TLS ACK. */ |
| conn->push_buf = os_malloc(1); |
| } |
| |
| if (conn->push_buf == NULL) |
| return NULL; |
| out_data = wpabuf_alloc_ext_data(conn->push_buf, conn->push_buf_len); |
| if (out_data == NULL) |
| os_free(conn->push_buf); |
| conn->push_buf = NULL; |
| conn->push_buf_len = 0; |
| return out_data; |
| } |
| |
| |
| struct wpabuf * tls_connection_server_handshake(void *tls_ctx, |
| struct tls_connection *conn, |
| const struct wpabuf *in_data, |
| struct wpabuf **appl_data) |
| { |
| return NULL; |
| } |
| |
| |
| struct wpabuf * tls_connection_encrypt(void *tls_ctx, |
| struct tls_connection *conn, |
| const struct wpabuf *in_data) |
| { |
| PRInt32 res; |
| struct wpabuf *buf; |
| |
| wpa_printf(MSG_DEBUG, "NSS: encrypt %d bytes", |
| (int) wpabuf_len(in_data)); |
| res = PR_Send(conn->fd, wpabuf_head(in_data), wpabuf_len(in_data), 0, |
| 0); |
| if (res < 0) { |
| wpa_printf(MSG_ERROR, "NSS: Encryption failed"); |
| return NULL; |
| } |
| if (conn->push_buf == NULL) |
| return NULL; |
| buf = wpabuf_alloc_ext_data(conn->push_buf, conn->push_buf_len); |
| if (buf == NULL) |
| os_free(conn->push_buf); |
| conn->push_buf = NULL; |
| conn->push_buf_len = 0; |
| return buf; |
| } |
| |
| |
| struct wpabuf * tls_connection_decrypt(void *tls_ctx, |
| struct tls_connection *conn, |
| const struct wpabuf *in_data) |
| { |
| PRInt32 res; |
| struct wpabuf *out; |
| |
| wpa_printf(MSG_DEBUG, "NSS: decrypt %d bytes", |
| (int) wpabuf_len(in_data)); |
| if (conn->pull_buf) { |
| wpa_printf(MSG_DEBUG, "%s - %lu bytes remaining in " |
| "pull_buf", __func__, |
| (unsigned long) conn->pull_buf_len); |
| os_free(conn->pull_buf); |
| } |
| conn->pull_buf = os_malloc(wpabuf_len(in_data)); |
| if (conn->pull_buf == NULL) |
| return NULL; |
| os_memcpy(conn->pull_buf, wpabuf_head(in_data), wpabuf_len(in_data)); |
| conn->pull_buf_offset = conn->pull_buf; |
| conn->pull_buf_len = wpabuf_len(in_data); |
| |
| /* |
| * Even though we try to disable TLS compression, it is possible that |
| * this cannot be done with all TLS libraries. Add extra buffer space |
| * to handle the possibility of the decrypted data being longer than |
| * input data. |
| */ |
| out = wpabuf_alloc((wpabuf_len(in_data) + 500) * 3); |
| if (out == NULL) |
| return NULL; |
| |
| res = PR_Recv(conn->fd, wpabuf_mhead(out), wpabuf_size(out), 0, 0); |
| wpa_printf(MSG_DEBUG, "NSS: PR_Recv: %d", res); |
| if (res < 0) { |
| wpabuf_free(out); |
| return NULL; |
| } |
| wpabuf_put(out, res); |
| |
| return out; |
| } |
| |
| |
| int tls_connection_resumed(void *tls_ctx, struct tls_connection *conn) |
| { |
| return 0; |
| } |
| |
| |
| int tls_connection_set_cipher_list(void *tls_ctx, struct tls_connection *conn, |
| u8 *ciphers) |
| { |
| return -1; |
| } |
| |
| |
| int tls_get_cipher(void *tls_ctx, struct tls_connection *conn, |
| char *buf, size_t buflen) |
| { |
| return -1; |
| } |
| |
| |
| int tls_connection_enable_workaround(void *tls_ctx, |
| struct tls_connection *conn) |
| { |
| return -1; |
| } |
| |
| |
| int tls_connection_client_hello_ext(void *tls_ctx, struct tls_connection *conn, |
| int ext_type, const u8 *data, |
| size_t data_len) |
| { |
| return -1; |
| } |
| |
| |
| int tls_connection_get_failed(void *tls_ctx, struct tls_connection *conn) |
| { |
| return 0; |
| } |
| |
| |
| int tls_connection_get_read_alerts(void *tls_ctx, struct tls_connection *conn) |
| { |
| return 0; |
| } |
| |
| |
| int tls_connection_get_write_alerts(void *tls_ctx, |
| struct tls_connection *conn) |
| { |
| return 0; |
| } |
| |
| |
| int tls_connection_get_keyblock_size(void *tls_ctx, |
| struct tls_connection *conn) |
| { |
| return -1; |
| } |
| |
| |
| unsigned int tls_capabilities(void *tls_ctx) |
| { |
| return 0; |
| } |
| |
| |
| int tls_connection_set_session_ticket_cb(void *tls_ctx, |
| struct tls_connection *conn, |
| tls_session_ticket_cb cb, |
| void *ctx) |
| { |
| return -1; |
| } |