# HG changeset patch # Parent e9b69da9a0f8dca923f8fc2836b38fe6590c791a # # 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 environment 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/fips-check.c b/fips-check.c new file mode 100644 index 0000000..eceb031 --- /dev/null +++ b/fips-check.c @@ -0,0 +1,34 @@ +#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(); + return 0; +} diff --git a/fips.c b/fips.c index 23e3876..297ae99 100644 --- a/fips.c +++ b/fips.c @@ -35,30 +35,293 @@ #include "log.h" #include "xmalloc.h" +#include +#include #include +#include +#include +#include +#include #include +#include +#include static int fips_state = -1; +/* calculates HMAC of contents of a file given by filename using the hash + * algorithm specified by FIPS_HMAC_EVP in fips.h and placing the result into + * newly allacated memory - remember to free it when not needed anymore */ static int -fips_check_required_env(void) +hmac_file(const char *filename, u_char **hmac_out) +{ + int check = -1; + int fd; + struct stat fs; + void *hmap; + unsigned char *hmac; + unsigned char *hmac_rv = NULL; + + hmac = xmalloc(FIPS_HMAC_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) { + hmac_rv = HMAC(FIPS_HMAC_EVP(), FIPS_HMAC_KEY + , strlen(FIPS_HMAC_KEY), hmap, fs.st_size, hmac, NULL); + check = CHECK_OK; + munmap(hmap, fs.st_size); + } + close(fd); + +bail_out: + if (hmac_rv) { + check = CHECK_OK; + *hmac_out = hmac; + } else { + check = CHECK_FAIL; + *hmac_out = NULL; + free(hmac); + } + 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; + int rv = -1; + + 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"); + } + + exe[0] = 0; + n = readlink(exe_sl, exe, max_pathlen); + /* the file doesn't need to exist - procfs might not be mounted in + * chroot */ + if (n == -1) { + rv = CHECK_MISSING; + } else { + if (n < max_pathlen) { + exe[n] = 0; + rv = CHECK_OK; + } else { + rv = CHECK_FAIL; + } + } + return rv; +} + +/* Read HMAC from file chk, allocating enough memory to hold the HMAC and + * return it in *hmac. + * Remember to free() it when it's not needed anymore. + */ +static int +read_hmac(const char *chk, u_char **hmac) +{ + int check = -1; + int fdh, n; + u_char *hmac_in; + + *hmac = 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; + } + + hmac_in = xmalloc(FIPS_HMAC_LEN); + + n = read(fdh, (void *)hmac_in, FIPS_HMAC_LEN); + if (FIPS_HMAC_LEN != n) { + debug("fips: unable to read whole checksum from checksum file\n"); + free (hmac_in); + check = CHECK_FAIL; + } else { + check = CHECK_OK; + *hmac = hmac_in; + } +bail_out: + return check; +} + +static int +fips_hmac_self(void) +{ + int check = -1; + u_char *hmac = NULL, *hmac_chk = NULL; + char *exe, *chk; + + exe = xmalloc(PATH_MAX); + chk = xmalloc(PATH_MAX); + + /* we will need to add the suffix and the null terminator */ + check = get_executable_path(getpid(), exe + , PATH_MAX - strlen(CHECKSUM_SUFFIX) - 1); + if (CHECK_OK != check) + goto cleanup; + + strncpy(chk, exe, PATH_MAX); + strlcat(chk, CHECKSUM_SUFFIX, PATH_MAX); + + check = read_hmac(chk, &hmac_chk); + if (CHECK_OK != check) + goto cleanup; + + check = hmac_file(exe, &hmac); + if (CHECK_OK != check) + goto cleanup; + + check = memcmp(hmac, hmac_chk, FIPS_HMAC_LEN); + if (0 == check) { + check = CHECK_OK; + debug("fips: checksum matches\n"); + } else { + check = CHECK_FAIL; + debug("fips: checksum mismatch!\n"); + } + +cleanup: + free(hmac); + free(hmac_chk); + free(chk); + free(exe); + + return check; +} + +static int +fips_check_required_proc(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; + 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) +{ + return (NULL != getenv(SSH_FORCE_FIPS_ENV)); +} + +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_hmac_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"); + } + } 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: 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(void) { diff --git a/fips.h b/fips.h index a115a61..3404684 100644 --- a/fips.h +++ b/fips.h @@ -1,5 +1,5 @@ /* - * 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 @@ -27,6 +27,15 @@ #include "sshkey.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 ".hmac" +#define FIPS_HMAC_KEY "HMAC_KEY:OpenSSH-FIPS@SLE" +#define FIPS_HMAC_EVP EVP_sha256 +#define FIPS_HMAC_LEN 32 + +void fips_ssh_init(void); typedef enum { FIPS_FILTER_CIPHERS, @@ -34,6 +43,12 @@ typedef enum { FIPS_FILTER_KEX_ALGS } fips_filters; +typedef enum { + CHECK_OK = 0, + CHECK_FAIL, + CHECK_MISSING +} fips_checksum_status; + int fips_mode(void); int fips_correct_dgst(int); int fips_dgst_min(void); @@ -41,4 +56,3 @@ enum fp_type fips_correct_fp_type(enum fp_type); int fips_filter_crypto(char **, fips_filters); #endif - diff --git a/sftp-server.c b/sftp-server.c index 359204f..d6395fd 100644 --- a/sftp-server.c +++ b/sftp-server.c @@ -53,6 +53,8 @@ char *sftp_realpath(const char *, char *); /* sftp-realpath.c */ +#include "fips.h" + /* Our verbosity */ static LogLevel log_level = SYSLOG_LEVEL_ERROR; @@ -1576,6 +1578,9 @@ sftp_server_main(int argc, char **argv, struct passwd *user_pw) 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); diff --git a/ssh.c b/ssh.c index 98b6ce7..dce28fd 100644 --- a/ssh.c +++ b/ssh.c @@ -113,6 +113,8 @@ #include "ssh-pkcs11.h" #endif +#include "fips.h" + extern char *__progname; /* Saves a copy of argv for setproctitle emulation */ @@ -630,6 +632,10 @@ main(int ac, char **av) struct addrinfo *addrs = NULL; size_t n, len; + /* initialize fips - can go before ssh_malloc_init(), since that is a + * OpenBSD-only thing (as of OpenSSH 7.6p1) */ + fips_ssh_init(); + /* Ensure that fds 0, 1 and 2 are open or directed to /dev/null */ sanitise_stdfd(); diff --git a/sshd.c b/sshd.c index b2146a6..6092f0f 100644 --- a/sshd.c +++ b/sshd.c @@ -1505,6 +1505,10 @@ main(int ac, char **av) Authctxt *authctxt; struct connection_info *connection_info = NULL; + /* initialize fips - can go before ssh_malloc_init(), since that is a + * OpenBSD-only thing (as of OpenSSH 7.6p1) */ + fips_ssh_init(); + #ifdef HAVE_SECUREWARE (void)set_auth_parameters(ac, av); #endif