# HG changeset patch # Parent 717873621cf4991164c61caafd9ac07473231f10 # Simple implementation of FIPS 140-2 selfchecks. Use OpenSSL to generate and # verify checksums of binaries. Any hash iused in OpenSSH can be used (MD5 would # obviously be a poor choice, since OpenSSL would barf and abort immediately in # FIPS mode). SHA-2 seems to be a reasonable choice. # # The logic of the checks is as follows: decide whether FIPS mode is mandated # (either by checking /proc/sys/crypto/fips_enabled or envoroinment variable # SSH_FORCE_FIPS. In FIPS mode, checksums are required to match (inability to # retrieve pre-calculated hash is a fatal error). In non-FIPS mode the checks # still must be performed, unless the hashes are not installed. Thus if the hash # file is not found (or the hash matches), proceed in non-FIPS mode and abort # otherwise. diff --git a/openssh-6.6p1/fips-check.c b/openssh-6.6p1/fips-check.c new file mode 100644 --- /dev/null +++ b/openssh-6.6p1/fips-check.c @@ -0,0 +1,37 @@ +#include "includes.h" +#include +#include +#include +#include +#include +#include +#include +#include + +#include "digest.h" +#include "fips.h" + +#include + +#define PROC_NAME_LEN 64 + +static const char *argv0; + +void +print_help_exit(int ev) +{ + fprintf(stderr, "%s <-c|-w> \n", argv0); + fprintf(stderr, " -c verify hash of 'file' against hash in 'checksum_file'\n"); + fprintf(stderr, " -w write hash of 'file' into 'checksum_file'\n"); + exit(ev); +} + +int +main(int argc, char **argv) +{ + + fips_ssh_init(); +// printf("SSL Error: %lx: %s", ERR_get_error(), ERR_get_string(ERR_get_error(), NULL)); + + return 0; +} diff --git a/openssh-6.6p1/fips.c b/openssh-6.6p1/fips.c --- a/openssh-6.6p1/fips.c +++ b/openssh-6.6p1/fips.c @@ -24,21 +24,342 @@ #include "includes.h" #include "fips.h" #include "digest.h" #include "key.h" #include "log.h" +#include "xmalloc.h" +#include + +#include +#include +#include +#include +#include +#include +#include #include +#include + +enum fips_checksum_status { + CHECK_OK = 0, + CHECK_FAIL, + CHECK_MISSING +}; static int fips_state = -1; +static char * +hex_fingerprint(u_int raw_len, u_char *raw) +{ + char *retval; + u_int i; + + /* reserve space for both the key hash and the string for the hash type */ + retval = malloc(3 * raw_len); + for (i = 0; i < raw_len; i++) { + char hex[4]; + snprintf(hex, sizeof(hex), "%02x:", raw[i]); + strlcat(retval, hex, raw_len * 3); + } + + return retval; +} + +/* calculates hash of contents of file given by filename using algorithm alg + * and placing the resukt into newly allacated memory - remember to free it + * when not needed anymore */ +static int +hash_file(const char *filename, int alg, u_char **hash_out) +{ + int check = -1; + int hash_len; + int fd; + struct stat fs; + void *hmap; + char *hash; + + hash_len = ssh_digest_bytes(alg); + hash = xmalloc(hash_len); + + fd = open(filename, O_RDONLY); + if (-1 == fd) + goto bail_out; + + if (-1 == fstat(fd, &fs)) + goto bail_out; + + hmap = mmap(NULL, fs.st_size, PROT_READ, MAP_SHARED, fd, 0); + + if ((void *)(-1) != hmap) { + check = ssh_digest_memory(alg, hmap, fs.st_size, hash, hash_len); + munmap(hmap, fs.st_size); + } + close(fd); + +bail_out: + if (0 == check) { + check = CHECK_OK; + *hash_out = hash; + } else { + check = CHECK_FAIL; + *hash_out = NULL; + free(hash); + } + return check; +} + +/* find pathname of binary of process with PID pid. exe is buffer expected to + * be capable of holding at least max_pathlen characters + */ +static int +get_executable_path(pid_t pid, char *exe, int max_pathlen) +{ + char exe_sl[PROC_EXE_PATH_LEN]; + int n; + + n = snprintf(exe_sl, sizeof(exe_sl), "/proc/%u/exe", pid); + if ((n <= 10) || (n >= max_pathlen)) { + fatal("error compiling filename of link to executable"); + } + + n = readlink(exe_sl, exe, max_pathlen); + if (n < max_pathlen) { + exe[n] = 0; + } else { + fatal("error getting executable pathname"); + } + return 0; +} + +/* Read checksum file chk, storing the algorithm used for generating it into + * *alg; allocate enough memory to hold the hash and return it in *hash. + * Remember to free() it when not needed anymore. + */ +static int +read_hash(const char *chk, int *alg, u_char **hash) +{ + int check = -1; + int hash_len; + int fdh, n; + char alg_c; + char *hash_in; + + *hash = NULL; + + fdh = open(chk, O_RDONLY); + if (-1 == fdh) { + switch (errno) { + case ENOENT: + check = CHECK_MISSING; + debug("fips: checksum file %s is missing\n", chk); + break; + default: + check = CHECK_FAIL; + debug("fips: ckecksum file %s not accessible\n", chk); + break; + + } + goto bail_out; + } + + n = read(fdh, &alg_c, 1); + if (1 != n) { + check = CHECK_FAIL; + goto bail_out; + } + + *alg = (int)alg_c; + hash_len = ssh_digest_bytes(*alg); + hash_in = xmalloc(hash_len); + + n = read(fdh, (void *)hash_in, hash_len); + if (hash_len != n) { + debug("fips: unable to read whole checksum from checksum file\n"); + free (hash_in); + check = CHECK_FAIL; + } else { + check = CHECK_OK; + *hash = hash_in; + } +bail_out: + return check; +} + +static int +fips_hash_self(void) +{ + int check = -1; + int alg; + u_char *hash, *hash_chk; + char *exe, *chk; + + exe = xmalloc(PATH_MAX); + chk = xmalloc(PATH_MAX); + + /* we will need to add the ".chk" suffix and the null terminator */ + check = get_executable_path(getpid(), exe + , PATH_MAX - strlen(CHECKSUM_SUFFIX) - 1); + + strncpy(chk, exe, PATH_MAX); + strlcat(chk, CHECKSUM_SUFFIX, PATH_MAX); + + check = read_hash(chk, &alg, &hash_chk); + if (CHECK_OK != check) + goto cleanup_chk; + + check = hash_file(exe, alg, &hash); + if (CHECK_OK != check) + goto cleanup; + + check = memcmp(hash, hash_chk, ssh_digest_bytes(alg)); + if (0 == check) { + check = CHECK_OK; + debug("fips: checksum matches\n"); + } else { + check = CHECK_FAIL; + debug("fips: checksum mismatch!\n"); + } + +cleanup: + free(hash); +cleanup_chk: + free(hash_chk); + free(chk); + free(exe); + + return check; +} + +static int +fips_check_required_proc(void) +{ + int fips_required = 0; + int fips_fd; + char fips_sys = 0; + + struct stat dummy; + if (-1 == stat(FIPS_PROC_PATH, &dummy)) { + switch (errno) { + case ENOENT: + case ENOTDIR: + break; + default: + fatal("Check for system-wide FIPS mode is required and %s cannot" + " be accessed for reason other than non-existence - aborting" + , FIPS_PROC_PATH); + break; + } + } else { + if (-1 == (fips_fd = open(FIPS_PROC_PATH, O_RDONLY))) + fatal("Check for system-wide FIPS mode is required and %s cannot" + " be opened for reading - aborting" + , FIPS_PROC_PATH); + if (1 > read(fips_fd, &fips_sys, 1)) + fatal("Check for system-wide FIPS mode is required and %s doesn't" + " return at least one character - aborting" + , FIPS_PROC_PATH); + close(fips_sys); + switch (fips_sys) { + case '0': + case '1': + fips_required = fips_sys - '0'; + break; + default: + fatal("Bogus character %c found in %s - aborting" + , fips_sys, FIPS_PROC_PATH); + } + } + return fips_required; +} + +static int +fips_check_required_env(void) +{ + int fips_required = 0; + char *env = getenv(SSH_FORCE_FIPS_ENV); + + if (env) { + errno = 0; + fips_required = strtol(env, NULL, 10); + if (errno) { + debug("bogus value in the %s environment variable, ignoring\n" + , SSH_FORCE_FIPS_ENV); + fips_required = 0; + } else + fips_required = 1; + } + return fips_required; +} + +static int +fips_required(void) +{ + int fips_requests = 0; + fips_requests += fips_check_required_proc(); + fips_requests += fips_check_required_env(); + return fips_requests; +} + +/* check whether FIPS mode is required and perform selfchecksum/selftest */ +void +fips_ssh_init(void) +{ + int checksum; + + checksum = fips_hash_self(); + + if (fips_required()) { + switch (checksum) { + case CHECK_OK: + debug("fips: mandatory checksum ok"); + break; + case CHECK_FAIL: + fatal("fips: mandatory checksum failed - aborting"); + break; + case CHECK_MISSING: + fatal("fips: mandatory checksum data missing - aborting"); + break; + default: + fatal("Fatal error: internal error at %s:%u" + , __FILE__, __LINE__); + break; + } + fips_state = FIPS_mode_set(1); + if (1 != fips_state) { + ERR_load_crypto_strings(); + u_long err = ERR_get_error(); + error("fips: OpenSSL error %lx: %s", err, ERR_error_string(err, NULL)); + fatal("fips: unable to set OpenSSL into FIPS mode - aborting" + , fips_state); + } + } else { + switch (checksum) { + case CHECK_OK: + debug("fips: checksum ok"); + break; + case CHECK_FAIL: + fatal("fips: checksum failed - aborting"); + break; + case CHECK_MISSING: + debug("fips: mandatory checksum data missing, but not required - continuing non-FIPS"); + break; + default: + fatal("Fatal error: internal error at %s:%u", + __FILE__, __LINE__); + break; + } + } + return; +} + int fips_mode() { if (-1 == fips_state) { fips_state = FIPS_mode(); if (fips_state) debug("FIPS mode initialized"); } diff --git a/openssh-6.6p1/fips.h b/openssh-6.6p1/fips.h --- a/openssh-6.6p1/fips.h +++ b/openssh-6.6p1/fips.h @@ -1,10 +1,10 @@ /* - * Copyright (c) 2012 Petr Cerny. All rights reserved. + * Copyright (c) 2012-2014 Petr Cerny. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the @@ -19,15 +19,22 @@ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #ifndef FIPS_H #define FIPS_H +#define SSH_FORCE_FIPS_ENV "SSH_FORCE_FIPS" +#define FIPS_PROC_PATH "/proc/sys/crypto/fips_enabled" + +#define PROC_EXE_PATH_LEN 64 +#define CHECKSUM_SUFFIX ".chk" + +void fips_ssh_init(void); int fips_mode(void); int fips_correct_dgst(int); int fips_dgst_min(void); enum fp_type fips_correct_fp_type(enum fp_type); #endif diff --git a/openssh-6.6p1/sftp-server.c b/openssh-6.6p1/sftp-server.c --- a/openssh-6.6p1/sftp-server.c +++ b/openssh-6.6p1/sftp-server.c @@ -47,16 +47,18 @@ #include "log.h" #include "misc.h" #include "match.h" #include "uidswap.h" #include "sftp.h" #include "sftp-common.h" +#include "fips.h" + /* helper */ #define get_int64() buffer_get_int64(&iqueue); #define get_int() buffer_get_int(&iqueue); #define get_string(lenp) buffer_get_string(&iqueue, lenp); /* Our verbosity */ static LogLevel log_level = SYSLOG_LEVEL_ERROR; @@ -1453,16 +1455,19 @@ sftp_server_main(int argc, char **argv, ssize_t len, olen, set_size; SyslogFacility log_facility = SYSLOG_FACILITY_AUTH; char *cp, *homedir = NULL, buf[4*4096]; long mask; extern char *optarg; extern char *__progname; + /* initialize fips */ + fips_ssh_init(); + __progname = ssh_get_progname(argv[0]); log_init(__progname, log_level, log_facility, log_stderr); pw = pwcopy(user_pw); while (!skipargs && (ch = getopt(argc, argv, "d:f:l:P:p:Q:u:m:cehR")) != -1) { switch (ch) { diff --git a/openssh-6.6p1/ssh.c b/openssh-6.6p1/ssh.c --- a/openssh-6.6p1/ssh.c +++ b/openssh-6.6p1/ssh.c @@ -420,16 +420,19 @@ main(int ac, char **av) struct stat st; struct passwd *pw; int timeout_ms; extern int optind, optreset; extern char *optarg; Forward fwd; struct addrinfo *addrs = NULL; + /* initialize fips */ + fips_ssh_init(); + /* Ensure that fds 0, 1 and 2 are open or directed to /dev/null */ sanitise_stdfd(); __progname = ssh_get_progname(av[0]); #ifndef HAVE_SETPROCTITLE /* Prepare for later setproctitle emulation */ /* Save argv so it isn't clobbered by setproctitle() emulation */ diff --git a/openssh-6.6p1/sshd.c b/openssh-6.6p1/sshd.c --- a/openssh-6.6p1/sshd.c +++ b/openssh-6.6p1/sshd.c @@ -1466,16 +1466,19 @@ main(int ac, char **av) u_int64_t ibytes, obytes; mode_t new_umask; Key *key; Key *pubkey; int keytype; Authctxt *authctxt; struct connection_info *connection_info = get_connection_info(0, 0); + /* initialize fips */ + fips_ssh_init(); + #ifdef HAVE_SECUREWARE (void)set_auth_parameters(ac, av); #endif __progname = ssh_get_progname(av[0]); /* Save argv. Duplicate so setproctitle emulation doesn't clobber it */ saved_argc = ac; rexec_argc = ac;