# HG changeset patch
# Parent  e265bbfd09491209436a3bffb1dfead25ee11556
#
# 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,34 @@
+#include "includes.h"
+#include <fcntl.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include "digest.h"
+#include "fips.h"
+
+#include <openssl/err.h>
+
+#define PROC_NAME_LEN	64
+
+static const char *argv0;
+
+void
+print_help_exit(int ev)
+{
+	fprintf(stderr, "%s <-c|-w> <file> <checksum_file>\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/openssh-6.6p1/fips.c b/openssh-6.6p1/fips.c
--- a/openssh-6.6p1/fips.c
+++ b/openssh-6.6p1/fips.c
@@ -26,40 +26,331 @@
 
 #include "fips.h"
 
 #include "dh.h"
 #include "digest.h"
 #include "key.h"
 #include "log.h"
 
+#include "xmalloc.h"
+#include <openbsd-compat/openssl-compat.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <string.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
 #include <openssl/crypto.h>
+#include <openssl/err.h>
+#include <openssl/hmac.h>
+
+enum fips_checksum_status {
+	CHECK_OK = 0,
+	CHECK_FAIL,
+	CHECK_MISSING
+};
 
 /* import from dh.c */
 extern int dh_grp_min;
 
 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 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
+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;
+	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);
+	return (NULL != 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_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)
 {
 	if (-1 == fips_state) {
 		fips_state = FIPS_mode();
 		if (fips_state)
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
@@ -22,17 +22,25 @@
  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 #ifndef FIPS_H
 #define FIPS_H
 
 #include "key.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);
 int	 fips_mode(void);
 int	 fips_correct_dgst(int);
 int	 fips_dgst_min(void);
 int	 fips_dh_grp_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;
 
@@ -1444,16 +1446,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: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
@@ -1391,16 +1391,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;
