/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (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 2005 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #pragma ident "%Z%%M% %I% %E% SMI" #include /* EXPORT DELETE START */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "boot_plat.h" #include "ramdisk.h" #include "wbcli.h" /* * Types of downloads */ #define MINIINFO "miniinfo" #define MINIROOT "miniroot" #define WANBOOTFS "wanbootfs" #define WANBOOT_RETRY_NOMAX -1 #define WANBOOT_RETRY_ROOT_MAX 50 #define WANBOOT_RETRY_MAX 5 #define WANBOOT_RETRY_SECS 5 #define WANBOOT_RETRY_MAX_SECS 30 /* * Our read requests should timeout after 25 seconds */ #define SOCKET_READ_TIMEOUT 25 /* * Experimentation has shown that an 8K download buffer is optimal */ static char buffer[8192]; bc_handle_t bc_handle; extern int determine_fstype_and_mountroot(char *); extern uint64_t get_ticks(void); /* * The following is used to determine whether the certs and private key * files will be in PEM format or PKCS12 format. 'use_p12' is zero * to use PEM format, and 1 when PKCS12 format is to be used. It is * done this way, as a global, so that it can be patched if needs be * using the OBP debugger. */ uint32_t use_p12 = 1; #define CONTENT_LENGTH "Content-Length" #define NONCELEN (2 * HMAC_DIGEST_LEN) /* two hex nibbles/byte */ #define WANBOOTFS_NONCE_FILE "/nonce" static char nonce[NONCELEN + 1]; enum URLtype { URLtype_wanbootfs = 0, URLtype_miniroot = 1 }; static char *URLtoCGIcontent[] = { "bootfs", "rootfs" }; #define CGIcontent(urltype) URLtoCGIcontent[urltype] /* Encryption algorithms */ typedef enum { ENCR_NONE, ENCR_3DES, ENCR_AES } encr_type_t; /* Hash algorithms */ typedef enum { HASH_NONE, HASH_HMAC_SHA1 } hash_type_t; /* * Keys ... */ static encr_type_t encr_type = ENCR_NONE; static unsigned char *g_encr_key = NULL; static hash_type_t hash_type = HASH_NONE; static unsigned char *g_hash_key = NULL; void print_errors(const char *func, http_handle_t handle) { char const *msg; ulong_t err; uint_t src; while ((err = http_get_lasterr(handle, &src)) != 0) { msg = http_errorstr(src, err); bootlog("wanboot", BOOTLOG_ALERT, "%s: errsrc %u, err %lu (0x%lx)", func, src, err, err); bootlog("wanboot", BOOTLOG_ALERT, "%s", msg); } } /* * This routine is called by a consumer to determine whether or not a * retry should be attempted. If a retry is in order (depends upon the * 'retry_cnt' and 'retry_max' arguments), then this routine will print a * message indicating this is the case and will determine an appropriate * "sleep" time before retrying. The "sleep" time will depend upon the * 'retry_cnt' and will max out at WANBOOT_RETRY_MAX_SECS. * * Returns: * B_TRUE = retry is in order * B_FALSE = retry limit exceeded */ boolean_t wanboot_retry(int retry_cnt, int retry_max) { unsigned int seconds; if (retry_max == WANBOOT_RETRY_NOMAX || retry_cnt <= retry_max) { seconds = WANBOOT_RETRY_SECS * retry_cnt; if (seconds > WANBOOT_RETRY_MAX_SECS) { seconds = WANBOOT_RETRY_MAX_SECS; } bootlog("wanboot", BOOTLOG_INFO, "Will retry in %d seconds ...", seconds); (void) sleep(seconds); return (B_TRUE); } else { bootlog("wanboot", BOOTLOG_INFO, "Maximum retries exceeded."); return (B_FALSE); } } /* * Determine which encryption algorithm the client is configured to use. * WAN boot determines which key to use by order of priority. That is * multiple encryption keys may exist in the PROM, but the first one found * (while searching in a preferred order) is the one that will be used. */ static void init_encryption(void) { static unsigned char key[WANBOOT_MAXKEYLEN]; size_t len = sizeof (key); if (bootinfo_get(BI_AES_KEY, (char *)&key, &len, NULL) == BI_E_SUCCESS) { encr_type = ENCR_AES; g_encr_key = key; } else if (bootinfo_get(BI_3DES_KEY, (char *)&key, &len, NULL) == BI_E_SUCCESS) { encr_type = ENCR_3DES; g_encr_key = key; } } /* * Determine whether the client is configured to use hashing. */ static void init_hashing(void) { static unsigned char key[WANBOOT_HMAC_KEY_SIZE]; size_t len = sizeof (key); if (bootinfo_get(BI_SHA1_KEY, (char *)&key, &len, NULL) == BI_E_SUCCESS) { hash_type = HASH_HMAC_SHA1; g_hash_key = key; } } /* * Read some CPU-specific rapidly-varying data (assumed to be of length * sizeof (hrtime_t) in the non-SPARC case), and digestify it to further * randomize the output. */ char * generate_nonce(void) { uint64_t t; SHA1_CTX c; unsigned char digest[HMAC_DIGEST_LEN]; uint_t nlen = sizeof (nonce); int err; /* * Read SPARC %tick register or x86 TSC */ t = get_ticks(); SHA1Init(&c); SHA1Update(&c, (const uint8_t *)&t, sizeof (t)); SHA1Final(digest, &c); err = octet_to_hexascii(digest, sizeof (digest), nonce, &nlen); if (err != 0) { bootlog("wanboot", BOOTLOG_CRIT, "cannot convert nonce to ASCII: error %d", err); return (NULL); } nonce[NONCELEN] = '\0'; return (nonce); } /* * Given a server URL, builds a URL to request one of the wanboot * datastreams. * * Returns: * -1 = Non-recoverable error * 0 = Success */ static int build_request_url(url_t *req_url, enum URLtype ut, const url_t *server_url) { char clid[WB_MAX_CID_LEN]; size_t clen; char wid[WB_MAX_CID_LEN * 2 + 1]; uint_t wlen; struct in_addr ip; struct in_addr mask; char *netstr; char *ppath; size_t plen; const char reqstr[] = "/?CONTENT=%s&IP=%s&CID=%s"; /* * Initialize the request */ *req_url = *server_url; /* * Build the network number string */ ipv4_getipaddr(&ip); ipv4_getnetmask(&mask); ip.s_addr = ip.s_addr & mask.s_addr; netstr = inet_ntoa(ip); /* * Get the wan id */ clen = sizeof (clid); if (bootinfo_get(BI_CLIENT_ID, clid, &clen, NULL) != BI_E_SUCCESS) { bootlog("wanboot", BOOTLOG_CRIT, "Cannot retrieve the client ID"); return (-1); } wlen = sizeof (wid); (void) octet_to_hexascii(clid, clen, wid, &wlen); /* * Build the request, making sure that the length of the * constructed URL falls within the supported maximum. */ plen = strlen(req_url->abspath); ppath = req_url->abspath + plen; if (snprintf(ppath, URL_MAX_PATHLEN - plen, reqstr, CGIcontent(ut), netstr, wid) >= URL_MAX_PATHLEN - plen) { bootlog("wanboot", BOOTLOG_CRIT, "The URL path length of the %s request is greater than " "the maximum of %d", CGIcontent(ut), URL_MAX_PATHLEN); return (-1); } /* * If the URL type requires a nonce, then supply it. * It will be returned in the reply to detect attempted * replays. */ if (ut == URLtype_wanbootfs) { char *n = generate_nonce(); if (n != NULL) { plen += strlen("&NONCE=") + NONCELEN; if (plen > URL_MAX_PATHLEN) return (-1); (void) strcat(req_url->abspath, "&NONCE="); (void) strcat(req_url->abspath, n); } } return (0); } /* * This routine reads data from an HTTP connection into a buffer. * * Returns: * 0 = Success * 1 = HTTP download error */ static int read_bytes(http_handle_t handle, char *buffer, size_t cnt) { int len; size_t i; for (i = 0; i < cnt; i += len) { len = http_read_body(handle, &buffer[i], cnt - i); if (len <= 0) { print_errors("http_read_body", handle); return (1); } } return (0); } /* * This routine compares two hash digests, one computed by the server and * the other computed by the client to verify that a transmitted message * was received without corruption. * * Notes: * The client only computes a digest if it is configured with a * hash key. If it is not, then the server should not have a hash * key for the client either and therefore should have sent a * zero filled digest. * * Returns: * B_TRUE = digest was verified * B_FALSE = digest did not verify */ static boolean_t verify_digests(const char *what, unsigned char *cdigest, unsigned char *sdigest) { static char null_digest[HMAC_DIGEST_LEN]; if (bcmp(sdigest, cdigest, HMAC_DIGEST_LEN) != 0) { bootlog("wanboot", BOOTLOG_CRIT, "%s: invalid hash digest", what); bootlog("wanboot", BOOTLOG_CRIT, "This may signify a client/server key mismatch"); if (bcmp(sdigest, null_digest, HMAC_DIGEST_LEN) == 0) { bootlog("wanboot", BOOTLOG_CRIT, "(client has key but wrong signature_type?)"); } else if (bcmp(cdigest, null_digest, HMAC_DIGEST_LEN) == 0) { bootlog("wanboot", BOOTLOG_CRIT, "(signature_type specified but no client key?)"); } bootlog("wanboot", BOOTLOG_CRIT, "or possible corruption of the image in transit"); return (B_FALSE); } return (B_TRUE); } /* * This routine reads the part of a multipart message that contains a * hash digest. Errors in reading the digest are differentiated from * other kinds of errors so that the caller can decide whether or * not a retry is worthwhile. * * Note: * The hash digest can either be an HMAC digest or it can be * a zero length message (representing no hash digest). * * Returns: * -1 = Non-recoverable error * 0 = Success * 1 = HTTP download error */ static int read_digest(const char *what, http_handle_t handle, unsigned char *sdigest) { char *lenstr; size_t digest_size; /* * Process the HMAC digest header. */ if (http_process_part_headers(handle, NULL) != 0) { print_errors("http_process_part_headers", handle); return (1); } lenstr = http_get_header_value(handle, CONTENT_LENGTH); if (lenstr == NULL) { bootlog("wanboot", BOOTLOG_ALERT, "%s: error getting digest length", what); return (1); } digest_size = (size_t)strtol(lenstr, NULL, 10); free(lenstr); /* * Validate the HMAC digest length. */ if (digest_size != HMAC_DIGEST_LEN) { bootlog("wanboot", BOOTLOG_CRIT, "%s: error validating response - invalid digest size", what); return (-1); } /* * Read the HMAC digest. */ if (read_bytes(handle, (char *)sdigest, digest_size) != 0) { bootlog("wanboot", BOOTLOG_ALERT, "%s: error reading digest", what); return (1); } return (0); } /* * This routine reads data from an HTTP connection and writes the data * to a ramdisk. It also, optionally computes a hash digest of the processed * data. This routine may be called to continue writing a previously aborted * write. If this is the case, then the offset will be non-zero and the write * pointer into the ramdisk will be positioned correctly by the caller. * * Returns: * -1 = Non-recoverable error * 0 = Success * 1 = HTTP download error */ static int write_msg_to_ramdisk(const char *what, int fd, http_handle_t handle, size_t ramdisk_size, off_t *offset, SHA1_CTX *sha) { int len; long nleft; static int bootlog_message_interval; static int bootlog_progress; int ret; /* * Read the data and write it to the ramdisk. */ if (*offset == 0) { bootlog_progress = 0; bootlog_message_interval = ramdisk_size / sizeof (buffer); if (bootlog_message_interval < 500) bootlog_message_interval /= 5; else bootlog_message_interval /= 50; bootlog("wanboot", BOOTLOG_VERBOSE, "Reading %s file system (%ld kB)", what, ramdisk_size / 1024); } else { bootlog("wanboot", BOOTLOG_VERBOSE, "Continuing read of %s file system (%ld kB)", what, ramdisk_size / 1024); } for (ret = 0; ret == 0 && *offset < ramdisk_size; *offset += len) { nleft = ramdisk_size - *offset; if (nleft > sizeof (buffer)) nleft = sizeof (buffer); len = http_read_body(handle, buffer, nleft); if (len <= 0) { print_errors("http_read_body", handle); /* * In the case of a partial failure, http_read_body() * returns into 'len', 1 - the number of bytes read. * So, a -65 means 64 bytes read and an error occurred. */ if (len != 0) { len = -(len + 1); } ret = 1; } if (sha != NULL) { HMACUpdate(sha, (uchar_t *)buffer, (size_t)len); } if (prom_write(fd, buffer, (size_t)len, 0, 0) != (ssize_t)len) { bootlog("wanboot", BOOTLOG_CRIT, "%s: write to ramdisk failed", what); ret = -1; continue; } if (bootlog_progress == bootlog_message_interval) { bootlog("wanboot", BOOTLOG_PROGRESS, "%s: Read %ld of %ld kB (%ld%%)", what, *offset / 1024, ramdisk_size / 1024, *offset * 100 / ramdisk_size); bootlog_progress = 0; } else { bootlog_progress++; } } if (ret == 0) { bootlog("wanboot", BOOTLOG_PROGRESS, "%s: Read %ld of %ld kB (%ld%%)", what, *offset / 1024, ramdisk_size / 1024, *offset * 100 / ramdisk_size); bootlog("wanboot", BOOTLOG_INFO, "%s: Download complete", what); } return (ret); } /* * This routine is called with a bootinfo parameter name. If the parameter * has a value it should be a URL, and this will be used to initialize the * http_url structure. * * Returns: * -1 = Non-recoverable error * 0 = Success * 1 = DHCP option not set */ static int get_url(char *name, url_t *url) { char buf[URL_MAX_STRLEN]; size_t len; int ret; bzero(buf, sizeof (buf)); len = sizeof (buf) - 1; if (bootinfo_get(name, buf, &len, NULL) != BI_E_SUCCESS || len == 0) { return (1); } /* * Parse the URL. */ ret = url_parse(buf, url); if (ret != URL_PARSE_SUCCESS) { bootlog("wanboot", BOOTLOG_CRIT, "Unable to parse URL %s", buf); return (-1); } return (0); } /* * This routine initiates an HTTP request and returns a handle so that * the caller can process the response. * * Notes: * Requests may be either secure or not. If the request is secure, then * this routine assumes that a wanboot file system exists and * uses its contents to provide the HTTP library with the information * that will be required by SSL. * * In order to facilitate transmission retries, this routine supports * range requests. A caller may request a range by providing a non-zero * offset. In which case, a range request is made that ranges from the * offet to the end of the file. * * If the client is configured to use an HTTP proxy, then this routine * will make the HTTP library aware of the proxy. * * Any HTTP errors encountered in downloading or processing the message * are not deemed unrecoverable errors. The caller can simply try the * request once again. * * Returns: * -1 = Non-recoverable error * 0 = Success * 1 = HTTP download error */ static int establish_http_connection(const char *what, http_handle_t *handlep, url_t *url, off_t offset) { static boolean_t is_auth_file_init = B_FALSE; static boolean_t is_proxy_init = B_FALSE; static boolean_t proxy_exists = B_FALSE; static url_hport_t proxy_hp; http_respinfo_t *resp; char buf[URL_MAX_STRLEN]; size_t len = sizeof (buf) - 1; int ret; /* Check for HTTP proxy */ if (!is_proxy_init && bootinfo_get(BI_HTTP_PROXY, buf, &len, NULL) == BI_E_SUCCESS && strlen(buf) > 0) { /* * Parse the hostport. */ ret = url_parse_hostport(buf, &proxy_hp, URL_DFLT_PROXY_PORT); if (ret == URL_PARSE_SUCCESS) { proxy_exists = B_TRUE; } else { bootlog("wanboot", BOOTLOG_CRIT, "%s is not set to a valid hostport value", BI_HTTP_PROXY); return (-1); } is_proxy_init = B_TRUE; } http_set_p12_format(use_p12); /* * Initialize the handle that will be used for the request. */ *handlep = http_srv_init(url); if (*handlep == NULL) { print_errors("http_srv_init", NULL); return (-1); } /* * Is the request a secure one? If it is, then we need to do further * setup. Search the wanboot file system for files that will be * needed by SSL. */ if (url->https) { char *cas; boolean_t client_authentication = B_FALSE; if (http_set_random_file(*handlep, "/dev/urandom") < 0) { print_errors("http_set_random_file", *handlep); (void) http_srv_close(*handlep); return (-1); } /* * We only need to initialize the CA once as it is not handle * specific. */ if (!is_auth_file_init) { if (http_set_certificate_authority_file(NB_CA_CERT_PATH) < 0) { print_errors( "http_set_certificate_authority_file", *handlep); (void) http_srv_close(*handlep); return (-1); } is_auth_file_init = B_TRUE; } /* * The client certificate and key will not exist unless * client authentication has been configured. If it is * configured then the webserver will have added these * files to the wanboot file system and the HTTP library * needs to be made aware of their existence. */ if ((cas = bootconf_get(&bc_handle, BC_CLIENT_AUTHENTICATION)) != NULL && strcmp(cas, "yes") == 0) { client_authentication = B_TRUE; if (http_set_client_certificate_file(*handlep, NB_CLIENT_CERT_PATH) < 0) { print_errors("http_set_client_certificate_file", *handlep); (void) http_srv_close(*handlep); return (-1); } if (http_set_private_key_file(*handlep, NB_CLIENT_KEY_PATH) < 0) { print_errors("http_set_private_key_file", *handlep); (void) http_srv_close(*handlep); return (-1); } } /* * We do not really need to set this unless client * authentication is configured or unless pkcs12 files * are used. */ if ((client_authentication || use_p12) && http_set_password(*handlep, WANBOOT_PASSPHRASE) < 0) { print_errors("http_set_password", *handlep); (void) http_srv_close(*handlep); return (-1); } } /* * If the client is using a proxy, tell the library. */ if (proxy_exists) { if (http_set_proxy(*handlep, &proxy_hp) != 0) { print_errors("http_set_proxy", *handlep); (void) http_srv_close(*handlep); return (-1); } } (void) http_set_socket_read_timeout(*handlep, SOCKET_READ_TIMEOUT); /* * Ok, connect to the webserver. */ if (http_srv_connect(*handlep) == -1) { print_errors("http_srv_connect", *handlep); (void) http_srv_close(*handlep); return (1); } /* * If the offset is 0, then we assume that we want the entire * message. If the offset is not 0, then we assume that we are * retrying a previously interrupted transfer and thus we make * a range request. */ if (offset == 0) { if ((ret = http_get_request(*handlep, url->abspath)) == 0) { bootlog("wanboot", BOOTLOG_VERBOSE, "%s: http_get_request: sent", what); } else { print_errors("http_get_request", *handlep); (void) http_srv_close(*handlep); return (1); } } else { if ((ret = http_get_range_request(*handlep, url->abspath, offset, 0)) == 0) { bootlog("wanboot", BOOTLOG_VERBOSE, "%s: http_get_range_request: sent", what); } else { print_errors("http_get_range_request", *handlep); (void) http_srv_close(*handlep); return (1); } } /* * Tell the library to read in the response headers. */ ret = http_process_headers(*handlep, &resp); if (ret == -1) { print_errors("http_process_headers", *handlep); (void) http_srv_close(*handlep); return (1); } /* * Check for a valid response code. */ if ((offset == 0 && resp->code != 200) || (offset != 0 && resp->code != 206)) { bootlog("wanboot", BOOTLOG_ALERT, "%s: Request returned code %d", what, resp->code); if (resp->statusmsg != NULL && resp->statusmsg[0] != '\0') bootlog("wanboot", BOOTLOG_ALERT, "%s", resp->statusmsg); http_free_respinfo(resp); (void) http_srv_close(*handlep); return (1); } http_free_respinfo(resp); /* * Success. */ return (0); } /* * This routine is called by get_miniinfo() to receive the reply * to the request for the miniroot metadata. The reply is a two * part multipart message. The first part of the message contains * the miniroot file size. The second part of the message contains * a hash digest of the miniroot as computed by the server. This * routine receives both message parts and returns them to the caller. * * Notes: * If the miniroot is going to be downloaded securely or if the * the server has no hash key for the client, then the hash digest * downloaded contains all zeros. * * Any HTTP errors encountered in downloading or processing the message * are not deemed unrecoverable errors. That is, get_miniinfo() * tries re-requesting the message and tries processing it again. * * Returns: * -1 = Non-recoverable error * 0 = Success * 1 = HTTP download error */ static int process_miniinfo(http_handle_t handle, size_t *mini_size, unsigned char *sdigest) { char *lenstr; size_t cnt; /* * Process the file size header. */ if (http_process_part_headers(handle, NULL) != 0) { print_errors("http_process_part_headers", handle); return (1); } lenstr = http_get_header_value(handle, CONTENT_LENGTH); if (lenstr == NULL) { bootlog("wanboot", BOOTLOG_ALERT, "%s: error getting length " "of first part of multipart message", MINIINFO); return (1); } cnt = (size_t)strtol(lenstr, NULL, 10); free(lenstr); if (cnt == 0 || cnt >= sizeof (buffer)) { bootlog("wanboot", BOOTLOG_ALERT, "%s: length of first part " "of multipart message not a legal size", MINIINFO); return (1); } if (read_bytes(handle, buffer, cnt) != 0) { bootlog("wanboot", BOOTLOG_ALERT, "%s: error reading miniroot size", MINIINFO); return (1); } buffer[cnt] = '\0'; *mini_size = (size_t)strtol(buffer, NULL, 10); if (*mini_size == 0) { bootlog("wanboot", BOOTLOG_ALERT, "%s: body of first part " "of multipart message not a legal size", MINIINFO); return (1); } return (read_digest(MINIINFO, handle, sdigest)); } /* * This routine is called by get_miniroot() to retrieve the miniroot * metadata (miniroot size and a hash digest). This routine sends an * HTTP GET request to the webserver to request the download of the * miniroot metadata and relies on process_miniinfo() to receive the * reply, process it and ultimately return to it the miniroot size and * the hash digest. * * Note: * Any HTTP errors encountered in downloading or processing the message * are not deemed unrecoverable errors. That is, get_miniinfo() should * try re-requesting the message and try processing again. * * Returns: * -1 = Non-recoverable error * 0 = Success */ int get_miniinfo(const url_t *server_url, size_t *mini_size, unsigned char *sdigest) { http_handle_t handle; url_t req_url; int retry_cnt = 0; int retry_max = WANBOOT_RETRY_MAX; int ret; /* * Build the URL to request the miniroot info. */ if (build_request_url(&req_url, URLtype_miniroot, server_url) == -1) { bootlog("wanboot", BOOTLOG_CRIT, "Can't build the URL to make the %s request", CGIcontent(URLtype_miniroot)); return (-1); } /* * Go get the miniroot info. If we fail reading the * response we re-request the info in its entirety. */ bootlog("wanboot", BOOTLOG_VERBOSE, "Downloading miniroot info"); do { if ((ret = establish_http_connection(MINIINFO, &handle, &req_url, 0)) < 0) { break; } else if (ret > 0) { if (wanboot_retry(++retry_cnt, retry_max)) { continue; } else { break; } } if ((ret = process_miniinfo(handle, mini_size, sdigest)) > 0) { if (!wanboot_retry(++retry_cnt, retry_max)) { (void) http_srv_close(handle); break; } } (void) http_srv_close(handle); } while (ret > 0); /* * Success. */ if (ret == 0) { bootlog("wanboot", BOOTLOG_VERBOSE, "Miniroot info download successful"); return (0); } else { bootlog("wanboot", BOOTLOG_CRIT, "Miniroot info download aborted"); return (-1); } } /* * This routine is called by get_miniroot() to receive the reply to * the request for the miniroot download. The miniroot is written * to ramdisk as it is received and a hash digest is optionally computed * as it does so. The miniroot is downloaded as one large message. * Because the message is so large, this routine is prepared to deal * with errors in the middle of download. If an error occurs during * download, then this message processes all received data up to the * point of the error and returns to get_miniroot() an error signifying * that a download error has occurred. Presumably, get_miniroot() * re-requests the remaining part of the miniroot not yet processed and * calls this routine back to process the reply. When this routine * returns succesfully, it returns a devpath to the ramdisk and the * computed hash (if computed). * * Note: * In order to facilitate reentry, the ramdisk is left open * and the original miniroot_size and HMAC handle are kept * static. * * Returns: * -1 = Non-recoverable error * 0 = Success * 1 = HTTP download error */ static int process_miniroot(http_handle_t handle, hash_type_t htype, size_t length, char **devpath, off_t *offset, unsigned char *cdigest) { static SHA1_CTX sha; static size_t miniroot_size; static int fd = -1; int ret; if (fd == -1) { if (htype == HASH_HMAC_SHA1) { bootlog("wanboot", BOOTLOG_INFO, "%s: Authentication will use HMAC-SHA1", MINIROOT); HMACInit(&sha, g_hash_key, WANBOOT_HMAC_KEY_SIZE); } miniroot_size = length; fd = create_ramdisk(RD_ROOTFS, miniroot_size, devpath); } if (prom_seek(fd, *offset) == -1) { bootlog("wanboot", BOOTLOG_CRIT, "%s: prom_seek error", MINIROOT); return (-1); } if ((ret = write_msg_to_ramdisk(MINIROOT, fd, handle, miniroot_size, offset, (htype == HASH_NONE) ? NULL : &sha)) != 0) { if (ret < 0) { /* * Reentry not supported. */ (void) prom_close(fd); } return (ret); } if (htype != HASH_NONE) { HMACFinal(&sha, g_hash_key, WANBOOT_HMAC_KEY_SIZE, cdigest); } (void) prom_close(fd); return (0); } /* * This routine retrieves the miniroot from the webserver. The miniroot * is retrieved in two steps. First a request is made to the server * to retrieve miniroot metadata (miniroot size and a hash digest). * The second request actually results in the download of the miniroot. * * This routine relies on get_miniinfo() to make and process * the request for the miniroot metadata and returns the * miniroot size and the hash digest of the miniroot as computed by * the server. * * If get_miniinfo() returns successfully, then this routine sends * an HTTP GET request to the webserver to request download of the * miniroot. This routine relies on process_miniroot() to receive * the reply, process it and ultimately return to it a device path to * a ramdisk containing the miniroot and a client computed hash digest. * This routine verifies that the client computed hash digest matches * the one retrieved by get_miniinfo(). * * If an error occurs in the transfer of the miniroot from the server * to the client, then the client re-requests the download of the * miniroot using a range request and only requests the part of the * miniroot not previously downloaded and written to ramdisk. The * process_miniroot() routine has the intelligence to recognize that * it is processing a range request. Errors not related to the actual * message download are deemed unrecoverable. * * Note: * If the client request for the miniroot is a secure request or * if the server is not configured with a hash key for the client, * then the hash digest downloaded from the server will contain * all zeros. This routine verifies that the server and client are * in-sync with respect to the need for hash verification. * * Returns: * -1 = Non-recoverable error * 0 = Success */ int get_miniroot(char **devpath) { http_handle_t handle; unsigned char cdigest[HMAC_DIGEST_LEN]; unsigned char sdigest[HMAC_DIGEST_LEN]; char *urlstr; url_t server_url; size_t mini_size; off_t offset; int plen; int retry_cnt = 0; int retry_max = WANBOOT_RETRY_ROOT_MAX; int ret; /* * Get the miniroot URL. */ if ((urlstr = bootconf_get(&bc_handle, BC_ROOT_SERVER)) == NULL) { bootlog("wanboot", BOOTLOG_CRIT, "Missing root_server URL"); return (-1); } else if (url_parse(urlstr, &server_url) != URL_PARSE_SUCCESS) { bootlog("wanboot", BOOTLOG_CRIT, "Unable to parse URL %s", urlstr); return (-1); } /* * We must get the miniroot info before we can request * the miniroot itself. */ if (get_miniinfo(&server_url, &mini_size, sdigest) != 0) { return (-1); } plen = sizeof (server_url.abspath); if ((urlstr = bootconf_get(&bc_handle, BC_ROOT_FILE)) == NULL || strlcpy(server_url.abspath, urlstr, plen) >= plen) { bootlog("wanboot", BOOTLOG_CRIT, "Cannot retrieve the miniroot path"); return (-1); } /* * Go get the miniroot. If we fail reading the response * then we re-request only the range we have yet to read, * unless the error was "unrecoverable" in which case we * re-request the entire file system. */ bootlog("wanboot", BOOTLOG_VERBOSE, "Downloading miniroot"); bzero(cdigest, sizeof (cdigest)); offset = 0; do { if ((ret = establish_http_connection(MINIROOT, &handle, &server_url, offset)) < 0) { break; } else if (ret > 0) { if (wanboot_retry(++retry_cnt, retry_max)) { continue; } else { break; } } if ((ret = process_miniroot(handle, server_url.https ? HASH_NONE : hash_type, mini_size, devpath, &offset, cdigest)) > 0) { if (!wanboot_retry(++retry_cnt, retry_max)) { (void) http_srv_close(handle); break; } } (void) http_srv_close(handle); } while (ret > 0); /* * Validate the computed digest against the one received. */ if (ret != 0 || !verify_digests(MINIROOT, cdigest, sdigest)) { bootlog("wanboot", BOOTLOG_CRIT, "Miniroot download aborted"); return (-1); } bootlog("wanboot", BOOTLOG_VERBOSE, "Miniroot download successful"); return (0); } /* * This routine is called to finish the decryption process. * Its purpose is to free the resources allocated by the * encryption init routines. */ static void encr_fini(encr_type_t etype, void *eh) { switch (etype) { case ENCR_3DES: des3_fini(eh); break; case ENCR_AES: aes_fini(eh); break; default: break; } } /* * This routine is called by process_wanbootfs() to read the encrypted * file system from ramdisk and decrypt it. This routine will rewrite * the file system back to ramdisk in place. The method of decryption * (algorithm) will have already been determined by process_wanbootfs() * and the cbc_handle passed to this routine will already have been * initialized appropriately. * * Returns: * -1 = Non-recoverable error * 0 = Success */ static int decrypt_wanbootfs(int fd, cbc_handle_t *ch, uint8_t *iv, size_t block_size, size_t wanbootfs_size) { size_t total; size_t len; size_t nleft; size_t max_read_size; max_read_size = (sizeof (buffer) / block_size) * block_size; for (total = 0; total < wanbootfs_size; total += len) { if (prom_seek(fd, total) == -1) { bootlog("wanboot", BOOTLOG_CRIT, "%s: prom_seek error", WANBOOTFS); return (-1); } nleft = wanbootfs_size - total; if (nleft > max_read_size) nleft = max_read_size; len = prom_read(fd, buffer, nleft, 0, 0); if (len != nleft) { bootlog("wanboot", BOOTLOG_CRIT, "%s: prom_read error", WANBOOTFS); return (-1); } if (!cbc_decrypt(ch, (uint8_t *)buffer, len, iv)) { bootlog("wanboot", BOOTLOG_CRIT, "%s: cbc decrypt error", WANBOOTFS); return (-1); } if (prom_seek(fd, total) == -1) { bootlog("wanboot", BOOTLOG_CRIT, "%s: prom_seek error", WANBOOTFS); return (-1); } if (prom_write(fd, buffer, len, 0, 0) != len) { bootlog("wanboot", BOOTLOG_CRIT, "%s: prom_write error", WANBOOTFS); return (-1); } } return (0); } /* * This routine is called by get_wanbootfs() to receive the reply to * the request for the wanboot file system. The reply is a multipart message. * The first part of the message is the file system (which may or may * not be encrypted). If encrypted, then the first block of the message * part is the CBC IV value used by the server to encrypt the remaining * part of the message part and is used by the client to decrypt it. The * second message part is a hash digest of the first part (the file * system) as computed by the server. If no hash key is configured * for the client, then the hash digest simply contains all zeros. This * routine receives both message parts. The file system is written to ramdisk * as it is received and simultaneously computes a hash digest (if a hash * key exists). Once the entire part is received, if the file system is * encrypted, it is read from ramdisk, decrypted and rewritten back to * ramdisk. The server computed hash digest is then read and along with the * ramdisk device path and the client computed hash digest is returned to the * caller. * * Notes: * In order to decrypt the file system and to compute the client * hash digest, an encryption key and a hash key is retrieved from * the PROM (or the wanboot interpreter). The non-existence of these * keys has implications on how the message response is processed and * it is assumed that the server is configured identically. * * Any HTTP errors encountered in downloading or processing the message * are not deemed unrecoverable errors. That is, get_wanbootfs() will * try re-requesting the message and will try processing it again. * * Returns: * -1 = Non-recoverable error * 0 = Success * 1 = HTTP download error */ static int process_wanbootfs(http_handle_t handle, char **devpath, unsigned char *cdigest, unsigned char *sdigest) { /* iv[] must be sized to store the largest possible encryption block */ uint8_t iv[WANBOOT_MAXBLOCKLEN]; cbc_handle_t ch; void *eh; SHA1_CTX sha; char *lenstr; size_t wanbootfs_size; size_t block_size; off_t offset; static int fd = -1; int ret; switch (hash_type) { case HASH_HMAC_SHA1: bootlog("wanboot", BOOTLOG_INFO, "%s: Authentication will use HMAC-SHA1", WANBOOTFS); HMACInit(&sha, g_hash_key, WANBOOT_HMAC_KEY_SIZE); break; case HASH_NONE: break; default: bootlog("wanboot", BOOTLOG_CRIT, "%s: unrecognized hash type", WANBOOTFS); return (-1); } switch (encr_type) { case ENCR_3DES: bootlog("wanboot", BOOTLOG_INFO, "%s: Decryption will use 3DES", WANBOOTFS); if (des3_init(&eh) != 0) { return (-1); } block_size = DES3_BLOCK_SIZE; des3_key(eh, g_encr_key); cbc_makehandle(&ch, eh, DES3_KEY_SIZE, block_size, DES3_IV_SIZE, des3_encrypt, des3_decrypt); break; case ENCR_AES: bootlog("wanboot", BOOTLOG_INFO, "%s: Decryption will use AES", WANBOOTFS); if (aes_init(&eh) != 0) { return (-1); } block_size = AES_BLOCK_SIZE; aes_key(eh, g_encr_key, AES_128_KEY_SIZE); cbc_makehandle(&ch, eh, AES_128_KEY_SIZE, block_size, AES_IV_SIZE, aes_encrypt, aes_decrypt); break; case ENCR_NONE: break; default: bootlog("wanboot", BOOTLOG_CRIT, "%s: unrecognized encryption type", WANBOOTFS); return (-1); } /* * Process the header. */ if (http_process_part_headers(handle, NULL) != 0) { print_errors("http_process_part_headers", handle); return (1); } lenstr = http_get_header_value(handle, CONTENT_LENGTH); if (lenstr == NULL) { bootlog("wanboot", BOOTLOG_ALERT, "%s: error getting length " "of first part of multipart message", WANBOOTFS); return (1); } wanbootfs_size = (size_t)strtol(lenstr, NULL, 10); free(lenstr); if (wanbootfs_size == 0) { bootlog("wanboot", BOOTLOG_ALERT, "%s: length of first part " "of multipart message not a legal size", WANBOOTFS); return (1); } /* * If encrypted, then read the iv. */ if (encr_type != ENCR_NONE) { if (read_bytes(handle, (char *)iv, block_size) != 0) { bootlog("wanboot", BOOTLOG_ALERT, "%s: error reading hash iv", WANBOOTFS); return (1); } wanbootfs_size -= block_size; if (hash_type != HASH_NONE) { HMACUpdate(&sha, (uchar_t *)iv, block_size); } } /* * We can only create the ramdisk once. So, if we've * already created it, then it means we've re-entered * this routine from an earlier partial failure. Use * the already existing ramdisk and seek back to the * beginning of the file. */ if (fd == -1) { fd = create_ramdisk(RD_BOOTFS, wanbootfs_size, devpath); } offset = 0; if (prom_seek(fd, offset) == -1) { bootlog("wanboot", BOOTLOG_CRIT, "%s: prom_seek error", WANBOOTFS); return (-1); } if ((ret = write_msg_to_ramdisk(WANBOOTFS, fd, handle, wanbootfs_size, &offset, (hash_type == HASH_NONE) ? NULL : &sha)) != 0) { if (ret < 0) { /* * Reentry not supported. */ (void) prom_close(fd); } return (ret); } if (hash_type != HASH_NONE) { HMACFinal(&sha, g_hash_key, WANBOOT_HMAC_KEY_SIZE, cdigest); } /* * If encrypted, then decrypt it. */ if (encr_type != ENCR_NONE) { ret = decrypt_wanbootfs(fd, &ch, iv, block_size, wanbootfs_size); if (ret != 0) { encr_fini(encr_type, eh); (void) prom_close(fd); return (-1); } encr_fini(encr_type, eh); } (void) prom_close(fd); return (read_digest(WANBOOTFS, handle, sdigest)); } /* * This routine sends an HTTP GET request to the webserver to * request the wanboot file system for the client. The server * will reply by sending a multipart message. This routine will rely * on process_wanbootfs() to receive the multipart message, process it * and ultimately return to it a device path to a ramdisk containing * the wanboot file system, a client computed hash digest and a * server computed hash digest. This routine will verify that the * client computed hash digest matches the one sent by the server. This * routine will also verify that the nonce received in the reply matches * the one sent in the request. * * If an error occurs in the transfer of the message from the server * to the client, then the client re-requests the download in its * entirety. Errors not related to the actual message download are * deemed unrecoverable. * * Returns: * -1 = Non-recoverable error * 0 = Success */ int get_wanbootfs(const url_t *server_url) { http_handle_t handle; unsigned char cdigest[HMAC_DIGEST_LEN]; unsigned char sdigest[HMAC_DIGEST_LEN]; url_t req_url; char *devpath; int ret; int fd; char buf[NONCELEN + 1]; int retry_cnt = 0; int retry_max = WANBOOT_RETRY_MAX; /* * Build the URL to request the wanboot file system. This URL * will include the CGI script name and the IP, CID, and * NONCE parameters. */ if (build_request_url(&req_url, URLtype_wanbootfs, server_url) == -1) { bootlog("wanboot", BOOTLOG_CRIT, "Can't build the URL to make the %s request", CGIcontent(URLtype_wanbootfs)); return (-1); } /* * Go get the wanboot file system. If we fail reading the * response we re-request the entire file system. */ bootlog("wanboot", BOOTLOG_VERBOSE, "Downloading wanboot file system"); bzero(cdigest, sizeof (cdigest)); do { if ((ret = establish_http_connection(WANBOOTFS, &handle, &req_url, 0)) < 0) { break; } else if (ret > 0) { if (wanboot_retry(++retry_cnt, retry_max)) { continue; } else { break; } } if ((ret = process_wanbootfs(handle, &devpath, cdigest, sdigest)) > 0) { if (!wanboot_retry(++retry_cnt, retry_max)) { (void) http_srv_close(handle); break; } } (void) http_srv_close(handle); } while (ret > 0); /* * Validate the computed digest against the one received. */ if (ret != 0 || !verify_digests(WANBOOTFS, cdigest, sdigest)) { bootlog("wanboot", BOOTLOG_CRIT, "The wanboot file system download aborted"); return (-1); } /* * Mount the wanboot file system. */ if (determine_fstype_and_mountroot(devpath) != VFS_SUCCESS) { bootlog("wanboot", BOOTLOG_CRIT, "Could not mount the wanboot filesystem."); bootlog("wanboot", BOOTLOG_CRIT, "This may signify a client/server key mismatch"); if (encr_type != ENCR_NONE) { bootlog("wanboot", BOOTLOG_CRIT, "(client has key but wrong encryption_type?)"); } else { bootlog("wanboot", BOOTLOG_CRIT, "(encryption_type specified but no client key?)"); } return (-1); } bootlog("wanboot", BOOTLOG_VERBOSE, "The wanboot file system has been mounted"); /* * The wanboot file system should contain a nonce. Read it * and compare it against the nonce sent in the request. */ if ((fd = open(WANBOOTFS_NONCE_FILE, O_RDONLY)) == -1) { bootlog("wanboot", BOOTLOG_CRIT, "No nonce found in the wanboot file system"); bootlog("wanboot", BOOTLOG_CRIT, "The wanboot file system download aborted"); return (-1); } if (read(fd, buf, NONCELEN) != NONCELEN || bcmp(nonce, buf, NONCELEN) != 0) { (void) close(fd); bootlog("wanboot", BOOTLOG_CRIT, "Invalid nonce found in the wanboot file system"); bootlog("wanboot", BOOTLOG_CRIT, "The wanboot file system download aborted"); return (-1); } (void) close(fd); bootlog("wanboot", BOOTLOG_VERBOSE, "The wanboot file system download was successful"); return (0); } static boolean_t init_netdev(char *bpath) { pnode_t anode; int proplen; static char netalias[OBP_MAXPATHLEN]; /* * Wanboot will either have loaded over the network (in which case * bpath will name a network device), or from CD-ROM or disk. In * both cases ensure that the 'net' alias corresponds to a network * device, and that if a network boot was performed that it is * identical to bpath. This is so that the interface name can always * be determined for CD-ROM or disk boots, and for manually-configured * network boots. The latter restriction may be relaxed in the future. */ anode = prom_alias_node(); if ((proplen = prom_getproplen(anode, "net")) > 0 && proplen < sizeof (netalias)) { (void) prom_getprop(anode, "net", (caddr_t)netalias); if (is_netdev(netalias)) { char *p; /* * Strip device arguments from netalias[]. */ if ((p = strchr(netalias, ':')) != NULL) { *p = '\0'; } /* * If bpath is a network device path, then v2path * will be a copy of this sans device arguments. */ if (!is_netdev(bpath) || strcmp(v2path, netalias) == 0) { /* * Stash the netdev_path bootprop value, then * initialize the hardware and return success. */ netdev_path = netalias; mac_init(netalias); return (B_TRUE); } bootlog("wanboot", BOOTLOG_CRIT, "wanboot requires that the 'net' alias refers to "); bootlog("wanboot", BOOTLOG_CRIT, "the network device path from which it loaded"); return (B_FALSE); } } /* * If we haven't established a device path for a network interface, * then we're doomed. */ bootlog("wanboot", BOOTLOG_CRIT, "No network device available for wanboot!"); bootlog("wanboot", BOOTLOG_CRIT, "(Ensure that the 'net' alias is set correctly)"); return (B_FALSE); } /* * This implementation of bootprog() is used solely by wanboot. * * The basic algorithm is as follows: * * - The wanboot options (those specified using the "-o" flag) are processed, * and if necessary the wanboot interpreter is invoked to collect other * options. * * - The wanboot filesystem (containing certificates, wanboot.conf file, etc.) * is then downloaded into the bootfs ramdisk, which is mounted for use * by OpenSSL, access to wanboot.conf, etc. * * - The wanboot miniroot is downloaded over http/https into the rootfs * ramdisk. The bootfs filesystem is unmounted, and the rootfs filesystem * is mounted. */ /* EXPORT DELETE END */ /*ARGSUSED*/ int bootprog(char *bpath, char *bargs, boolean_t user_specified_filename) { /* EXPORT DELETE START */ char *miniroot_path; url_t server_url; int ret; if (!init_netdev(bpath)) { return (-1); } if (!bootinfo_init()) { bootlog("wanboot", BOOTLOG_CRIT, "Cannot initialize bootinfo"); return (-1); } /* * Get default values from PROM, etc., process any boot arguments * (specified with the "-o" option), and initialize the interface. */ if (!wanboot_init_interface(wanboot_arguments)) { return (-1); } /* * Determine which encryption and hashing algorithms the client * is configured to use. */ init_encryption(); init_hashing(); /* * Get the bootserver value. Should be of the form: * http://host[:port]/abspath. */ ret = get_url(BI_BOOTSERVER, &server_url); if (ret != 0) { bootlog("wanboot", BOOTLOG_CRIT, "Unable to retrieve the bootserver URL"); return (-1); } /* * Get the wanboot file system and mount it. Contains metdata * needed by wanboot. */ if (get_wanbootfs(&server_url) != 0) { return (-1); } /* * Check that there is a valid wanboot.conf file in the wanboot * file system. */ if (bootconf_init(&bc_handle, NULL) != BC_E_NOERROR) { bootlog("wanboot", BOOTLOG_CRIT, "wanboot.conf error (code=%d)", bc_handle.bc_error_code); return (-1); } /* * Set the time */ init_boot_time(); /* * Verify that URLs in wanboot.conf can be reached, etc. */ if (!wanboot_verify_config()) { return (-1); } /* * Retrieve the miniroot. */ if (get_miniroot(&miniroot_path) != 0) { return (-1); } /* * We don't need the wanboot file system mounted anymore and * should unmount it so that we can mount the miniroot. */ (void) unmountroot(); /* * Mount the miniroot. */ if (determine_fstype_and_mountroot(miniroot_path) != VFS_SUCCESS) { bootlog("wanboot", BOOTLOG_CRIT, "Could not mount miniroot filesystem"); return (-1); } bootlog("wanboot", BOOTLOG_VERBOSE, "The miniroot has been mounted"); v2path = "/ramdisk-rootfs:a"; bootlog("wanboot", BOOTLOG_VERBOSE, "device path '%s'", v2path); /* * kernname (default-name) might have changed if mountroot() called * boot_nfs_mountroot(), and it called set_default_filename(). */ if (! user_specified_filename) (void) strcpy(filename, kernname); bootlog("wanboot", BOOTLOG_VERBOSE, "standalone = `%s', args = `%s'", filename, bargs); set_client_bootargs(filename, bargs); /* * We're done with the mac interface that was initialized by * mac_init() inside init_netdev(). */ mac_fini(); bootconf_end(&bc_handle); bootinfo_end(); /* EXPORT DELETE END */ return (0); }