/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2007 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. * * File: CLIENT.C */ #pragma ident "%Z%%M% %I% %E% SMI" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include extern int errno; #define OCSP_BUFSIZE 1024 typedef enum { KMF_RESPONSE_OCSP = 1, KMF_RESPONSE_FILE = 2 } KMF_RESPONSE_TYPE; #define TEMP_TEMPLATE "temp.XXXXXX" /* * This function will establish a socket to the host on the specified port. * If succeed, it return a socket descriptor; otherwise, return -1. */ static int init_socket(char *host, short port) { struct sockaddr_in sin; struct hostent *hp, hrec; int sockfd, opt, herrno; char hostbuf[BUFSIZ]; sin.sin_family = PF_INET; sin.sin_port = htons(port); if ((sin.sin_addr.s_addr = inet_addr(host)) == INADDR_NONE) { if ((hp = gethostbyname_r(host, &hrec, hostbuf, sizeof (hostbuf), &herrno)) == NULL) { return (-1); } (void) memcpy((char *)&sin.sin_addr, hp->h_addr, hp->h_length); } if ((sockfd = socket(PF_INET, SOCK_STREAM, 0)) < 0) { return (-1); } opt = 1; if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, (char *)&opt, sizeof (opt)) < 0) { (void) close(sockfd); return (-1); } if (connect(sockfd, (struct sockaddr *)&sin, sizeof (sin)) < 0) { (void) close(sockfd); return (-1); } return (sockfd); } /* * This function will connect to host on the port. * If succeed, return a socket descriptor; otherwise, return 0. */ static int connect_to_server(char *host, short port) { int retry = 1; int sd = 0; while (retry) { if ((sd = init_socket(host, port)) == -1) { if (errno == ECONNREFUSED) { retry = 1; (void) sleep(1); } else { retry = 0; } } else { retry = 0; } } return (sd); } static KMF_RETURN send_ocsp_request(int sock, char *reqfile, char *hostname) { KMF_RETURN ret = KMF_OK; int filefd, bytes, n, total = 0; char buf[OCSP_BUFSIZE]; struct stat s; char req_header[256]; static char req_format[] = "POST %s HTTP/1.0\r\n\ Content-Type: application/ocsp-request\r\n\ Content-Length: %d\r\n\r\n"; if ((filefd = open(reqfile, O_RDONLY)) == -1) { ret = KMF_ERR_OPEN_FILE; return (ret); } /* open the request file */ if (fstat(filefd, &s) < 0) { ret = KMF_ERR_OPEN_FILE; return (ret); } /* Send http header */ if (hostname != NULL) { (void) snprintf(req_header, 256, req_format, hostname, s.st_size); } else { (void) snprintf(req_header, 256, req_format, "/", s.st_size); } bytes = strlen(req_header); if ((n = write(sock, req_header, bytes)) < 0) { ret = KMF_ERR_SEND_REQUEST; goto exit; } /* Send the request content */ while ((bytes = read(filefd, buf, OCSP_BUFSIZE)) > 0) { if ((n = write(sock, buf, bytes)) < 0) { ret = KMF_ERR_SEND_REQUEST; goto exit; } total += n; (void) memset(buf, 0, sizeof (buf)); } exit: (void) close(filefd); return (ret); } /* * Perform a write that can handle EINTR. */ static int looping_write(int fd, void *buf, int len) { char *p = buf; int cc, len2 = 0; if (len == 0) return (0); do { cc = write(fd, p, len); if (cc < 0) { if (errno == EINTR) continue; return (cc); } else if (cc == 0) { return (len2); } else { p += cc; len2 += cc; len -= cc; } } while (len > 0); return (len2); } /* * This function will get the response from the server, check the http status * line, and write the response content to a file. If this is a OCSP response, * it will check the content type also. */ static KMF_RETURN get_encoded_response(int sock, KMF_RESPONSE_TYPE resptype, int filefd, unsigned int maxsecs) { int ret = KMF_OK; char *buf = NULL; int buflen = 0; int offset = 0; int search_offset; const int buf_incre = OCSP_BUFSIZE; /* 1 KB at a time */ const int maxBufSize = 8 * buf_incre; /* 8 KB max */ const char *CRLF = "\r\n"; const char *headerEndMark = "\r\n\r\n"; const char *httpprotocol = "HTTP/"; const int CRLFlen = strlen(CRLF); const int marklen = strlen(headerEndMark); const int httplen = strlen(httpprotocol); char *headerEnd = NULL; boolean_t EOS = B_FALSE; const char *httpcode = NULL; const char *contenttype = NULL; int contentlength = 0; int bytes = 0; char *statusLineEnd = NULL; char *space = NULL; char *nextHeader = NULL; struct pollfd pfd; int sock_flag; int poll_ret; boolean_t timeout = B_FALSE; /* set O_NONBLOCK flag on socket */ if ((sock_flag = fcntl(sock, F_GETFL, 0)) == -1) { return (KMF_ERR_RECV_RESPONSE); } sock_flag |= O_NONBLOCK; if (fcntl(sock, F_SETFL, sock_flag) == -1) { return (KMF_ERR_RECV_RESPONSE); } /* set up poll */ pfd.fd = sock; pfd.events = POLLIN; /* * First read HTTP status line and headers. We will read up to at * least the end of the HTTP headers */ do { if ((buflen - offset) < buf_incre) { buflen += buf_incre; buf = realloc(buf, buflen + 1); if (buf == NULL) { ret = KMF_ERR_MEMORY; goto out; } } pfd.revents = 0; poll_ret = poll(&pfd, 1, maxsecs * MILLISEC); if (poll_ret == 0) { timeout = B_TRUE; break; } else if (poll_ret < 0) { ret = KMF_ERR_RECV_RESPONSE; goto out; } else { if (pfd.revents & (POLLERR | POLLHUP | POLLNVAL)) { ret = KMF_ERR_RECV_RESPONSE; goto out; } } bytes = read(sock, buf + offset, buf_incre); if (bytes < 0) { if (errno == EWOULDBLOCK) { /* no data this time */ continue; } else { ret = KMF_ERR_RECV_RESPONSE; goto out; } } else if (bytes == 0) { /* no more data */ EOS = B_TRUE; } else { /* bytes > 0 */ search_offset = (offset - marklen) > 0 ? offset - marklen : 0; offset += bytes; *(buf + offset) = '\0'; /* NULL termination */ headerEnd = strstr((const char *)buf + search_offset, headerEndMark); } } while ((!headerEnd) && (EOS == B_FALSE) && (buflen < maxBufSize)); if (timeout == B_TRUE) { ret = KMF_ERR_RECV_TIMEOUT; goto out; } else if (headerEnd == NULL) { /* could not find the end of headers */ ret = KMF_ERR_BAD_HTTP_RESPONSE; goto out; } /* * Parse the HTTP status line, which will look like this: * "HTTP/1.1 200 OK". */ statusLineEnd = strstr((const char *)buf, CRLF); if (statusLineEnd == NULL) { ret = KMF_ERR_BAD_HTTP_RESPONSE; goto out; } *statusLineEnd = '\0'; space = strchr((const char *)buf, ' '); if (space == NULL || (strncasecmp((const char *)buf, httpprotocol, httplen) != 0)) { ret = KMF_ERR_BAD_HTTP_RESPONSE; goto out; } /* * Check the HTTP status code. If it is not 200, the HTTP response * is not good. */ httpcode = space + 1; space = strchr(httpcode, ' '); if (space == NULL) { ret = KMF_ERR_BAD_HTTP_RESPONSE; goto out; } *space = 0; if (strcmp(httpcode, "200") != 0) { ret = KMF_ERR_BAD_HTTP_RESPONSE; goto out; } /* * Parse the HTTP headers in the buffer. Save content-type and * content-length only. */ nextHeader = statusLineEnd + CRLFlen; *headerEnd = '\0'; /* terminate */ do { char *thisHeaderEnd = NULL; char *value = NULL; char *colon = strchr(nextHeader, ':'); if (colon == NULL) { ret = KMF_ERR_BAD_HTTP_RESPONSE; goto out; } *colon = '\0'; value = colon + 1; if (*value != ' ') { ret = KMF_ERR_BAD_HTTP_RESPONSE; goto out; } value++; thisHeaderEnd = strstr(value, CRLF); if (thisHeaderEnd != NULL) *thisHeaderEnd = '\0'; if (strcasecmp(nextHeader, "content-type") == 0) { contenttype = value; } else if (strcasecmp(nextHeader, "content-length") == 0) { contentlength = atoi(value); } if (thisHeaderEnd != NULL) { nextHeader = thisHeaderEnd + CRLFlen; } else { nextHeader = NULL; } } while (nextHeader && (nextHeader < (headerEnd + CRLFlen))); /* Check the contenttype if this is an OCSP response */ if (resptype == KMF_RESPONSE_OCSP) { if (contenttype == NULL) { ret = KMF_ERR_BAD_HTTP_RESPONSE; goto out; } else if (strcasecmp(contenttype, "application/ocsp-response") != 0) { ret = KMF_ERR_BAD_HTTP_RESPONSE; goto out; } } /* Now we are ready to read the body of the response */ offset = offset - (int)(headerEnd - (const char *)buf) - marklen; if (offset) { /* move all data to the beginning of the buffer */ (void) memmove(buf, headerEnd + marklen, offset); } /* resize buffer to only what's needed to hold the current response */ buflen = (1 + (offset-1) / buf_incre) * buf_incre; while ((EOS == B_FALSE) && ((contentlength == 0) || (offset < contentlength)) && (buflen < maxBufSize)) { /* we still need to receive more content data */ if ((buflen - offset) < buf_incre) { buflen += buf_incre; buf = realloc(buf, buflen + 1); if (buf == NULL) { ret = KMF_ERR_MEMORY; goto out; } } pfd.revents = 0; poll_ret = poll(&pfd, 1, maxsecs * MILLISEC); if (poll_ret == 0) { timeout = B_TRUE; break; } else if (poll_ret < 0) { ret = KMF_ERR_RECV_RESPONSE; goto out; } else { if (pfd.revents & (POLLERR | POLLHUP | POLLNVAL)) { ret = KMF_ERR_RECV_RESPONSE; goto out; } } bytes = read(sock, buf + offset, buf_incre); if (bytes < 0) { if (errno == EWOULDBLOCK) { continue; } else { ret = KMF_ERR_RECV_RESPONSE; goto out; } } else if (bytes == 0) { /* no more data */ EOS = B_TRUE; } else { offset += bytes; } } if (timeout == B_TRUE) { ret = KMF_ERR_RECV_TIMEOUT; goto out; } else if (((contentlength != 0) && (offset < contentlength)) || offset == 0) { ret = KMF_ERR_BAD_HTTP_RESPONSE; goto out; } /* write to the file */ if (looping_write(filefd, buf, offset) != offset) { ret = KMF_ERR_WRITE_FILE; } out: free(buf); return (ret); } KMF_RETURN kmf_get_encoded_ocsp_response(KMF_HANDLE_T handle, char *reqfile, char *hostname, int port, char *proxy, int proxy_port, char *respfile, unsigned int maxsecs) { KMF_RETURN ret = KMF_OK; int sock, respfd; char http_hostname[256]; int final_proxy_port, final_port; CLEAR_ERROR(handle, ret); if (ret != KMF_OK) return (ret); if (hostname == NULL || reqfile == NULL || respfile == NULL) { return (KMF_ERR_BAD_PARAMETER); } final_proxy_port = (proxy_port == 0 || proxy_port == -1) ? 80 : proxy_port; final_port = (port == 0 || port == -1) ? 80 : port; /* Connect to server */ if (proxy != NULL) { sock = connect_to_server(proxy, final_proxy_port); } else { sock = connect_to_server(hostname, final_port); } if (sock == -1) { return (KMF_ERR_CONNECT_SERVER); } /* Send the OCSP request */ if (proxy != NULL) { (void) snprintf(http_hostname, sizeof (http_hostname), "http://%s:%d", hostname, final_port); ret = send_ocsp_request(sock, reqfile, http_hostname); } else { ret = send_ocsp_request(sock, reqfile, NULL); } if (ret != KMF_OK) { goto out; } /* Retrieve the OCSP response */ if (maxsecs == 0) { maxsecs = 30; /* default poll time limit is 30 seconds */ } if ((respfd = open(respfile, O_CREAT |O_RDWR | O_EXCL, 0600)) == -1) { ret = KMF_ERR_OPEN_FILE; } else { ret = get_encoded_response(sock, KMF_RESPONSE_OCSP, respfd, maxsecs); (void) close(respfd); } out: (void) close(sock); return (ret); } static KMF_RETURN send_download_request(int sock, char *hostname, int port, boolean_t is_proxy, char *loc) { KMF_RETURN ret = KMF_OK; char url[256]; char req_header[1024]; static char req_format[] = "GET %s HTTP/1.0\r\n\ Host: %s:%d\r\n\ Accept: */*\r\n\r\n"; if (is_proxy) { (void) snprintf(url, sizeof (url), "http://%s:%d/%s", hostname, port, loc); } else { (void) snprintf(url, sizeof (url), "/%s", loc); } (void) snprintf(req_header, sizeof (req_header), req_format, url, hostname, port); if (write(sock, req_header, strlen(req_header)) < 0) { ret = KMF_ERR_SEND_REQUEST; } return (ret); } static KMF_RETURN download_file(char *uri, char *proxy, int proxy_port, unsigned int maxsecs, int filefd) { KMF_RETURN ret = KMF_OK; xmlURIPtr uriptr; int sock; boolean_t is_proxy; int final_proxy_port; char *hostname = NULL; char *path = NULL; int port; if (uri == NULL || filefd == -1) return (KMF_ERR_BAD_PARAMETER); /* Parse URI */ uriptr = xmlParseURI(uri); if (uriptr == NULL) { ret = KMF_ERR_BAD_URI; goto out; } if (uriptr->scheme == NULL || strncasecmp(uriptr->scheme, "http", 4) != 0) { ret = KMF_ERR_BAD_URI; /* we support http only */ goto out; } /* get the host name */ hostname = uriptr->server; if (hostname == NULL) { ret = KMF_ERR_BAD_URI; goto out; } /* get the port number */ port = uriptr->port; if (port == 0) { port = 80; } /* Get the path */ path = uriptr->path; if (path == NULL) { ret = KMF_ERR_BAD_URI; goto out; } /* Connect to server */ if (proxy != NULL) { final_proxy_port = (proxy_port == 0 || proxy_port == -1) ? 80 : proxy_port; is_proxy = B_TRUE; sock = connect_to_server(proxy, final_proxy_port); } else { is_proxy = B_FALSE; sock = connect_to_server(hostname, port); } if (sock == -1) { ret = KMF_ERR_CONNECT_SERVER; goto out; } /* Send the request */ ret = send_download_request(sock, hostname, port, is_proxy, path); if (ret != KMF_OK) { goto out; } /* Retrieve the response */ ret = get_encoded_response(sock, KMF_RESPONSE_FILE, filefd, maxsecs == 0 ? 30 : maxsecs); if (ret != KMF_OK) { goto out; } out: if (uriptr != NULL) xmlFreeURI(uriptr); if (sock != -1) (void) close(sock); return (ret); } KMF_RETURN kmf_download_crl(KMF_HANDLE_T handle, char *uri, char *proxy, int proxy_port, unsigned int maxsecs, char *crlfile, KMF_ENCODE_FORMAT *pformat) { KMF_RETURN ret = KMF_OK; char *filename = NULL; char tempfn[MAXPATHLEN]; boolean_t temp_created = B_FALSE; mode_t old_mode; int fd = -1, tmpfd = -1; CLEAR_ERROR(handle, ret); if (ret != KMF_OK) return (ret); if (uri == NULL || crlfile == NULL || pformat == NULL) return (KMF_ERR_BAD_PARAMETER); if ((fd = open(crlfile, O_CREAT |O_RDWR | O_EXCL, 0644)) == -1) return (KMF_ERR_OPEN_FILE); /* * Download the file and save it to a temp file. To make rename() * happy, the temp file needs to be created in the same directory as * the target file. */ if ((filename = strdup(crlfile)) == NULL) { ret = KMF_ERR_MEMORY; goto out; } (void) snprintf(tempfn, MAXPATHLEN, "%s/%s", dirname(filename), TEMP_TEMPLATE); old_mode = umask(077); tmpfd = mkstemp(tempfn); (void) umask(old_mode); if (tmpfd == -1) { ret = KMF_ERR_INTERNAL; goto out; } else { temp_created = B_TRUE; } ret = download_file(uri, proxy, proxy_port, maxsecs, tmpfd); (void) close(tmpfd); if (ret != KMF_OK) { goto out; } /* Check if it is a CRL file and get its format */ if (kmf_is_crl_file(handle, tempfn, pformat) != KMF_OK) { ret = KMF_ERR_BAD_CRLFILE; goto out; } /* Finally, change the temp filename to the target crlfile */ if (rename(tempfn, crlfile) == -1) { ret = KMF_ERR_WRITE_FILE; goto out; } out: if (filename != NULL) free(filename); if (ret != KMF_OK && temp_created == B_TRUE) (void) unlink(tempfn); if (fd != -1) (void) close(fd); return (ret); } KMF_RETURN kmf_download_cert(KMF_HANDLE_T handle, char *uri, char *proxy, int proxy_port, unsigned int maxsecs, char *certfile, KMF_ENCODE_FORMAT *pformat) { KMF_RETURN ret = KMF_OK; char *filename = NULL; char tempfn[MAXPATHLEN]; boolean_t temp_created = B_FALSE; mode_t old_mode; int fd = -1, tmpfd = -1; CLEAR_ERROR(handle, ret); if (ret != KMF_OK) return (ret); if (uri == NULL || certfile == NULL || pformat == NULL) return (KMF_ERR_BAD_PARAMETER); if ((fd = open(certfile, O_CREAT |O_RDWR | O_EXCL, 0644)) == -1) return (KMF_ERR_OPEN_FILE); /* * Download the file and save it to a temp file. To make rename() * happy, the temp file needs to be created in the same directory as * the target file. */ if ((filename = strdup(certfile)) == NULL) { ret = KMF_ERR_MEMORY; goto out; } (void) snprintf(tempfn, MAXPATHLEN, "%s/%s", dirname(filename), TEMP_TEMPLATE); old_mode = umask(077); tmpfd = mkstemp(tempfn); (void) umask(old_mode); if (tmpfd == -1) { ret = KMF_ERR_INTERNAL; goto out; } else { temp_created = B_TRUE; } ret = download_file(uri, proxy, proxy_port, maxsecs, tmpfd); (void) close(tmpfd); if (ret != KMF_OK) { goto out; } /* Check if it is a Cert file and get its format */ if (kmf_is_cert_file(handle, tempfn, pformat) != KMF_OK) { ret = KMF_ERR_BAD_CERTFILE; goto out; } /* Finally, change the temp filename to the target filename */ if (rename(tempfn, certfile) == -1) { ret = KMF_ERR_WRITE_FILE; goto out; } out: if (filename != NULL) free(filename); if (ret != KMF_OK && temp_created == B_TRUE) (void) unlink(tempfn); if (fd != -1) (void) close(fd); return (ret); } KMF_RETURN kmf_get_ocsp_for_cert(KMF_HANDLE_T handle, KMF_DATA *user_cert, KMF_DATA *ta_cert, KMF_DATA *response) { KMF_POLICY_RECORD *policy; KMF_RETURN ret = KMF_OK; char *hostname = NULL, *host_uri = NULL, *proxyname = NULL; char *proxy_port_s = NULL; int host_port = 0, proxy_port = 0; char ocsp_reqname[MAXPATHLEN]; char ocsp_respname[MAXPATHLEN]; KMF_X509EXT_AUTHINFOACCESS aia; int i; boolean_t found = B_FALSE; KMF_X509EXT_ACCESSDESC *access_info; xmlURIPtr uriptr = NULL; KMF_ATTRIBUTE attrlist[10]; int numattr = 0; CLEAR_ERROR(handle, ret); if (ret != KMF_OK) return (ret); if (user_cert == NULL || ta_cert == NULL || response == NULL) return (KMF_ERR_BAD_PARAMETER); policy = handle->policy; /* Create an OCSP request */ kmf_set_attr_at_index(attrlist, numattr, KMF_ISSUER_CERT_DATA_ATTR, ta_cert, sizeof (KMF_DATA)); numattr++; kmf_set_attr_at_index(attrlist, numattr, KMF_USER_CERT_DATA_ATTR, user_cert, sizeof (KMF_DATA)); numattr++; /* * Create temporary files to hold the OCSP request & response data. */ (void) strlcpy(ocsp_reqname, OCSPREQ_TEMPNAME, sizeof (ocsp_reqname)); if (mkstemp(ocsp_reqname) == -1) { return (KMF_ERR_INTERNAL); } (void) strlcpy(ocsp_respname, OCSPRESP_TEMPNAME, sizeof (ocsp_respname)); if (mkstemp(ocsp_respname) == -1) { return (KMF_ERR_INTERNAL); } kmf_set_attr_at_index(attrlist, numattr, KMF_OCSP_REQUEST_FILENAME_ATTR, ocsp_respname, strlen(ocsp_respname)); numattr++; ret = kmf_create_ocsp_request(handle, numattr, attrlist); if (ret != KMF_OK) { goto out; } if (policy->VAL_OCSP_BASIC.uri_from_cert == 0) { if (policy->VAL_OCSP_BASIC.responderURI == NULL) { ret = KMF_ERR_OCSP_POLICY; goto out; } host_uri = policy->VAL_OCSP_BASIC.responderURI; } else { /* * Get the responder URI from certificate * Authority Information Access * thru OID_PKIX_AD_OCSP */ ret = kmf_get_cert_auth_info_access(user_cert, &aia); if (ret != KMF_OK) { goto out; } for (i = 0; i < aia.numberOfAccessDescription; i++) { access_info = &aia.AccessDesc[i]; if (IsEqualOid(&access_info->AccessMethod, (KMF_OID *)&KMFOID_PkixAdOcsp)) { host_uri = (char *)access_info->AccessLocation.Data; found = B_TRUE; break; } } if (!found) { ret = KMF_ERR_OCSP_POLICY; goto out; } } /* Parse the URI string; get the hostname and port */ uriptr = xmlParseURI(host_uri); if (uriptr == NULL) { ret = KMF_ERR_BAD_URI; goto out; } if (strncasecmp(uriptr->scheme, "http", 4) != 0) { ret = KMF_ERR_BAD_URI; /* we support http only */ goto out; } hostname = uriptr->server; if (hostname == NULL) { ret = KMF_ERR_BAD_URI; goto out; } host_port = uriptr->port; if (host_port == 0) host_port = 80; /* get the proxy info */ if (policy->VAL_OCSP_BASIC.proxy != NULL) { char *last; proxyname = strtok_r(policy->VAL_OCSP_BASIC.proxy, ":", &last); proxy_port_s = strtok_r(NULL, "\0", &last); if (proxy_port_s != NULL) { proxy_port = strtol(proxy_port_s, NULL, 0); } else { proxy_port = 8080; /* default */ } } /* * Send the request to an OCSP responder and receive an * OCSP response. */ ret = kmf_get_encoded_ocsp_response(handle, ocsp_reqname, hostname, host_port, proxyname, proxy_port, ocsp_respname, 30); if (ret != KMF_OK) { goto out; } ret = kmf_read_input_file(handle, ocsp_respname, response); out: (void) unlink(ocsp_reqname); (void) unlink(ocsp_respname); if (uriptr != NULL) xmlFreeURI(uriptr); return (ret); }