| /* |
| * TLS support code for CUPS using Google BoringSSL. |
| * |
| * Copyright 2007-2016 by Apple Inc. |
| * Copyright 1997-2007 by Easy Software Products, all rights reserved. |
| * |
| * These coded instructions, statements, and computer programs are the |
| * property of Apple Inc. and are protected by Federal copyright |
| * law. Distribution and use rights are outlined in the file "LICENSE.txt" |
| * which should have been included with this file. If this file is |
| * file is missing or damaged, see the license at "http://www.cups.org/". |
| * |
| * This file is subject to the Apple OS-Developed Software exception. |
| */ |
| |
| /**** This file is included from tls.c ****/ |
| |
| /* |
| * Local globals... |
| */ |
| |
| #include "cups-private.h" |
| #include "debug-internal.h" |
| #include "http.h" |
| #include "thread-private.h" |
| #include <openssl/err.h> |
| #include <openssl/ssl.h> |
| |
| #include <sys/stat.h> |
| |
| static char *tls_keypath = NULL; |
| /* Server cert keychain path */ |
| static int tls_options = -1,/* Options for TLS connections */ |
| tls_min_version = _HTTP_TLS_1_0, |
| tls_max_version = _HTTP_TLS_MAX; |
| |
| |
| /* |
| * Local functions... |
| */ |
| |
| static BIO_METHOD * _httpBIOMethods(void); |
| static int http_bio_write(BIO *h, const char *buf, int num); |
| static int http_bio_read(BIO *h, char *buf, int size); |
| static int http_bio_puts(BIO *h, const char *str); |
| static long http_bio_ctrl(BIO *h, int cmd, long arg1, void *arg2); |
| static int http_bio_new(BIO *h); |
| static int http_bio_free(BIO *data); |
| |
| static BIO_METHOD http_bio_methods = |
| { |
| BIO_TYPE_SSL, |
| "http", |
| http_bio_write, |
| http_bio_read, |
| http_bio_puts, |
| NULL, /* http_bio_gets, */ |
| http_bio_ctrl, |
| http_bio_new, |
| http_bio_free, |
| NULL, |
| }; |
| |
| /* |
| * 'cupsMakeServerCredentials()' - Make a self-signed certificate and private key pair. |
| * |
| * @since CUPS 2.0/OS 10.10@ |
| */ |
| |
| int /* O - 1 on success, 0 on failure */ |
| cupsMakeServerCredentials( |
| const char *path, /* I - Path to keychain/directory */ |
| const char *common_name, /* I - Common name */ |
| int num_alt_names, /* I - Number of subject alternate names */ |
| const char **alt_names, /* I - Subject Alternate Names */ |
| time_t expiration_date) /* I - Expiration date */ |
| { |
| int pid, /* Process ID of command */ |
| status; /* Status of command */ |
| char command[1024], /* Command */ |
| *argv[12], /* Command-line arguments */ |
| *envp[1000], /* Environment variables */ |
| infofile[1024], /* Type-in information for cert */ |
| seedfile[1024]; /* Random number seed file */ |
| int envc, /* Number of environment variables */ |
| bytes; /* Bytes written */ |
| cups_file_t *fp; /* Seed/info file */ |
| int infofd; /* Info file descriptor */ |
| char temp[1024], /* Temporary directory name */ |
| crtfile[1024], /* Certificate filename */ |
| keyfile[1024]; /* Private key filename */ |
| |
| DEBUG_printf(("cupsMakeServerCredentials(path=\"%s\", common_name=\"%s\", num_alt_names=%d, alt_names=%p, expiration_date=%d)", path, common_name, num_alt_names, alt_names, (int)expiration_date)); |
| |
| return 0; |
| } |
| |
| |
| /* |
| * '_httpCreateCredentials()' - Create credentials in the internal format. |
| */ |
| |
| http_tls_credentials_t /* O - Internal credentials */ |
| _httpCreateCredentials( |
| cups_array_t *credentials) /* I - Array of credentials */ |
| { |
| (void)credentials; |
| |
| return (NULL); |
| } |
| |
| |
| /* |
| * '_httpFreeCredentials()' - Free internal credentials. |
| */ |
| |
| void |
| _httpFreeCredentials( |
| http_tls_credentials_t credentials) /* I - Internal credentials */ |
| { |
| (void)credentials; |
| } |
| |
| |
| /* |
| * '_httpBIOMethods()' - Get the OpenSSL BIO methods for HTTP connections. |
| */ |
| |
| static BIO_METHOD * /* O - BIO methods for OpenSSL */ |
| _httpBIOMethods(void) |
| { |
| return (&http_bio_methods); |
| } |
| |
| |
| /* |
| * 'http_bio_ctrl()' - Control the HTTP connection. |
| */ |
| |
| static long /* O - Result/data */ |
| http_bio_ctrl(BIO *h, /* I - BIO data */ |
| int cmd, /* I - Control command */ |
| long arg1, /* I - First argument */ |
| void *arg2) /* I - Second argument */ |
| { |
| switch (cmd) |
| { |
| default : |
| return (0); |
| |
| case BIO_CTRL_RESET : |
| h->ptr = NULL; |
| return (0); |
| |
| case BIO_C_SET_FILE_PTR : |
| h->ptr = arg2; |
| h->init = 1; |
| return (1); |
| |
| case BIO_C_GET_FILE_PTR : |
| if (arg2) |
| { |
| *((void **)arg2) = h->ptr; |
| return (1); |
| } |
| else |
| return (0); |
| |
| case BIO_CTRL_DUP : |
| case BIO_CTRL_FLUSH : |
| return (1); |
| } |
| } |
| |
| |
| /* |
| * 'http_bio_free()' - Free OpenSSL data. |
| */ |
| |
| static int /* O - 1 on success, 0 on failure */ |
| http_bio_free(BIO *h) /* I - BIO data */ |
| { |
| if (!h) |
| return (0); |
| |
| if (h->shutdown) |
| { |
| h->init = 0; |
| h->flags = 0; |
| } |
| |
| return (1); |
| } |
| |
| |
| /* |
| * 'http_bio_new()' - Initialize an OpenSSL BIO structure. |
| */ |
| |
| static int /* O - 1 on success, 0 on failure */ |
| http_bio_new(BIO *h) /* I - BIO data */ |
| { |
| if (!h) |
| return (0); |
| |
| h->init = 0; |
| h->num = 0; |
| h->ptr = NULL; |
| h->flags = 0; |
| |
| return (1); |
| } |
| |
| |
| /* |
| * 'http_bio_puts()' - Send a string for OpenSSL. |
| */ |
| |
| static int /* O - Bytes written */ |
| http_bio_puts(BIO *h, /* I - BIO data */ |
| const char *str) /* I - String to write */ |
| { |
| return (send(((http_t *)h->ptr)->fd, str, strlen(str), 0)); |
| } |
| |
| |
| /* |
| * 'http_bio_read()' - Read data for OpenSSL. |
| */ |
| |
| static int /* O - Bytes read */ |
| http_bio_read(BIO *h, /* I - BIO data */ |
| char *buf, /* I - Buffer */ |
| int size) /* I - Number of bytes to read */ |
| { |
| http_t *http; /* HTTP connection */ |
| |
| |
| http = (http_t *)h->ptr; |
| |
| if (!http->blocking) |
| { |
| /* |
| * Make sure we have data before we read... |
| */ |
| |
| while (!_httpWait(http, http->wait_value, 0)) |
| { |
| if (http->timeout_cb && (*http->timeout_cb)(http, http->timeout_data)) |
| continue; |
| |
| http->error = ETIMEDOUT; |
| |
| return (-1); |
| } |
| } |
| |
| return (recv(http->fd, buf, size, 0)); |
| } |
| |
| |
| /* |
| * 'http_bio_write()' - Write data for OpenSSL. |
| */ |
| |
| static int /* O - Bytes written */ |
| http_bio_write(BIO *h, /* I - BIO data */ |
| const char *buf, /* I - Buffer to write */ |
| int num) /* I - Number of bytes to write */ |
| { |
| return (send(((http_t *)h->ptr)->fd, buf, num, 0)); |
| } |
| |
| |
| /* |
| * '_httpTLSInitialize()' - Initialize the TLS stack. |
| */ |
| |
| void |
| _httpTLSInitialize(void) |
| { |
| SSL_library_init(); |
| } |
| |
| |
| /* |
| * '_httpTLSPending()' - Return the number of pending TLS-encrypted bytes. |
| */ |
| |
| size_t /* O - Bytes available */ |
| _httpTLSPending(http_t *http) /* I - HTTP connection */ |
| { |
| return (SSL_pending(http->tls)); |
| } |
| |
| |
| /* |
| * '_httpTLSRead()' - Read from a SSL/TLS connection. |
| */ |
| |
| int /* O - Bytes read */ |
| _httpTLSRead(http_t *http, /* I - Connection to server */ |
| char *buf, /* I - Buffer to store data */ |
| int len) /* I - Length of buffer */ |
| { |
| return (SSL_read((SSL *)(http->tls), buf, len)); |
| } |
| |
| |
| /* |
| * '_httpTLSSetOptions()' - Set TLS protocol and cipher suite options. |
| */ |
| |
| void |
| _httpTLSSetOptions(int options, int min_version, int max_version) /* I - Options */ |
| { |
| tls_options = options; |
| tls_min_version = min_version; |
| tls_max_version = max_version; |
| } |
| |
| |
| /* |
| * '_httpTLSStart()' - Set up SSL/TLS support on a connection. |
| */ |
| |
| int /* O - 0 on success, -1 on failure */ |
| _httpTLSStart(http_t *http) /* I - Connection to server */ |
| { |
| char hostname[256], /* Hostname */ |
| *hostptr; /* Pointer into hostname */ |
| |
| SSL_CTX *context; /* Context for encryption */ |
| BIO *bio; /* BIO data */ |
| const char *message = NULL;/* Error message */ |
| |
| DEBUG_printf(("3_httpTLSStart(http=%p)", (void *)http)); |
| |
| if (tls_options < 0) |
| { |
| DEBUG_puts("4_httpTLSStart: Setting defaults."); |
| _cupsSetDefaults(); |
| DEBUG_printf(("4_httpTLSStart: tls_options=%x", tls_options)); |
| } |
| |
| if (http->mode == _HTTP_MODE_SERVER && !tls_keypath) |
| { |
| DEBUG_puts("4_httpTLSStart: cupsSetServerCredentials not called."); |
| http->error = errno = EINVAL; |
| http->status = HTTP_STATUS_ERROR; |
| _cupsSetError(IPP_STATUS_ERROR_INTERNAL, _("Server credentials not set."), 1); |
| |
| return (-1); |
| } |
| |
| context = SSL_CTX_new(TLS_method()); |
| SSL_CTX_set_min_proto_version(context, tls_min_version); |
| SSL_CTX_set_max_proto_version(context, tls_max_version); |
| |
| bio = BIO_new(_httpBIOMethods()); |
| BIO_ctrl(bio, BIO_C_SET_FILE_PTR, 0, (char *)http); |
| |
| http->tls = SSL_new(context); |
| SSL_set_bio(http->tls, bio, bio); |
| |
| /* http->tls retains an internal reference to the SSL_CTX. */ |
| SSL_CTX_free(context); |
| |
| if (http->mode == _HTTP_MODE_CLIENT) |
| { |
| SSL_set_connect_state(http->tls); |
| |
| /* |
| * Client: get the hostname to use for TLS... |
| */ |
| |
| if (httpAddrLocalhost(http->hostaddr)) |
| { |
| strlcpy(hostname, "localhost", sizeof(hostname)); |
| } |
| else |
| { |
| /* |
| * Otherwise make sure the hostname we have does not end in a trailing dot. |
| */ |
| |
| strlcpy(hostname, http->hostname, sizeof(hostname)); |
| if ((hostptr = hostname + strlen(hostname) - 1) >= hostname && |
| *hostptr == '.') |
| *hostptr = '\0'; |
| } |
| SSL_set_tlsext_host_name(http->tls, hostname); |
| } |
| else |
| { |
| /* @@@ TODO @@@ */ |
| _cupsSetError(IPP_STATUS_ERROR_INTERNAL, "Server not supported", 0); |
| } |
| |
| |
| if (SSL_do_handshake(http->tls) != 1) |
| { |
| unsigned long error; /* Error code */ |
| char buf[256]; |
| |
| while ((error = ERR_get_error()) != 0) |
| { |
| ERR_error_string_n(error, buf, sizeof(buf)); |
| DEBUG_printf(("8http_setup_ssl: %s", buf)); |
| } |
| |
| SSL_free(http->tls); |
| http->tls = NULL; |
| |
| http->error = errno; |
| http->status = HTTP_STATUS_ERROR; |
| |
| if (!message) |
| message = _("Unable to establish a secure connection to host."); |
| |
| _cupsSetError(IPP_STATUS_ERROR_CUPS_PKI, message, 1); |
| |
| return (-1); |
| } |
| |
| _cups_globals_t *cg = _cupsGlobals(); |
| if (cg->server_cert_cb) |
| { |
| int error = 0; |
| X509 *peer_certificate = SSL_get_peer_certificate(http->tls); |
| if (peer_certificate) |
| { |
| ASN1_BIT_STRING *key = X509_get0_pubkey_bitstr(peer_certificate); |
| cups_array_t *credentials = cupsArrayNew(NULL, NULL); |
| |
| if (credentials != NULL) |
| { |
| httpAddCredential(credentials, key->data, key->length); |
| error = cg->server_cert_cb(http, http->tls, credentials, cg->server_cert_data); |
| httpFreeCredentials(credentials); |
| } |
| X509_free(peer_certificate); |
| } |
| |
| if (error != 0) |
| { |
| SSL_free(http->tls); |
| http->tls = NULL; |
| http->error = errno = EINVAL; |
| http->status = HTTP_STATUS_ERROR; |
| _cupsSetError(IPP_STATUS_ERROR_INTERNAL, _("Client rejected the server certificate."), 1); |
| } |
| |
| return (error); |
| } |
| |
| return (0); |
| } |
| |
| |
| /* |
| * '_httpTLSStop()' - Shut down SSL/TLS on a connection. |
| */ |
| |
| void |
| _httpTLSStop(http_t *http) /* I - Connection to server */ |
| { |
| unsigned long error; /* Error code */ |
| |
| switch (SSL_shutdown(http->tls)) |
| { |
| case 1 : |
| break; |
| |
| case -1 : |
| _cupsSetError(IPP_STATUS_ERROR_INTERNAL, |
| "Fatal error during SSL shutdown!", 0); |
| default : |
| while ((error = ERR_get_error()) != 0) |
| { |
| char buf[256]; |
| ERR_error_string_n(error, buf, sizeof(buf)); |
| _cupsSetError(IPP_STATUS_ERROR_INTERNAL, buf, 0); |
| } |
| break; |
| } |
| |
| SSL_free(http->tls); |
| http->tls = NULL; |
| } |
| |
| /* |
| * '_httpTLSWrite()' - Write to a SSL/TLS connection. |
| */ |
| |
| int /* O - Bytes written */ |
| _httpTLSWrite(http_t *http, /* I - Connection to server */ |
| const char *buf, /* I - Buffer holding data */ |
| int len) /* I - Length of buffer */ |
| { |
| int result; /* Return value */ |
| |
| |
| DEBUG_printf(("2http_write_ssl(http=%p, buf=%p, len=%d)", http, buf, len)); |
| |
| result = SSL_write((SSL *)(http->tls), buf, len); |
| |
| DEBUG_printf(("3http_write_ssl: Returning %d.", result)); |
| |
| return result; |
| } |