# comply with FIPS 140-2 by using only approved crypto algorithms # when OpenSSL is detected to be running in FIPS mode # # HG changeset patch # Parent 6536ed881743cbf05afe962021b985f9b1eab495 diff --git a/openssh-6.5p1/Makefile.in b/openssh-6.5p1/Makefile.in --- a/openssh-6.5p1/Makefile.in +++ b/openssh-6.5p1/Makefile.in @@ -72,17 +72,17 @@ LIBSSH_OBJS=authfd.o authfile.o bufaux.o cipher-bf1.o cipher-ctr.o cipher-3des1.o cleanup.o \ compat.o compress.o crc32.o deattack.o fatal.o hostfile.o \ log.o match.o md-sha256.o moduli.o nchan.o packet.o \ readpass.o rsa.o ttymodes.o xmalloc.o addrmatch.o \ atomicio.o key.o dispatch.o kex.o mac.o uidswap.o uuencode.o misc.o \ monitor_fdpass.o rijndael.o ssh-dss.o ssh-ecdsa.o ssh-rsa.o dh.o \ kexdh.o kexgex.o kexdhc.o kexgexc.o bufec.o kexecdh.o kexecdhc.o \ msg.o progressmeter.o dns.o entropy.o gss-genr.o umac.o umac128.o \ - jpake.o schnorr.o ssh-pkcs11.o krl.o auditstub.o + jpake.o schnorr.o ssh-pkcs11.o krl.o auditstub.o fips.o SSHOBJS= ssh.o readconf.o clientloop.o sshtty.o \ sshconnect.o sshconnect1.o sshconnect2.o mux.o \ roaming_common.o roaming_client.o SSHDOBJS=sshd.o auth-rhosts.o auth-passwd.o auth-rsa.o auth-rh-rsa.o \ audit.o audit-bsm.o audit-linux.o platform.o \ sshpty.o sshlogin.o servconf.o serverloop.o \ diff --git a/openssh-6.5p1/auth-rsa.c b/openssh-6.5p1/auth-rsa.c --- a/openssh-6.5p1/auth-rsa.c +++ b/openssh-6.5p1/auth-rsa.c @@ -15,17 +15,17 @@ */ #include "includes.h" #include #include #include -#include +#include #include #include #include #include #include "xmalloc.h" #include "rsa.h" @@ -42,16 +42,17 @@ #include "hostfile.h" #include "auth.h" #ifdef GSSAPI #include "ssh-gss.h" #endif #include "monitor_wrap.h" #include "ssh.h" #include "misc.h" +#include "fips.h" /* import */ extern ServerOptions options; /* * Session identifier that is used to bind key exchange and authentication * responses to a particular session. */ @@ -83,45 +84,54 @@ auth_rsa_generate_challenge(Key *key) if (BN_mod(challenge, challenge, key->rsa->n, ctx) == 0) fatal("auth_rsa_generate_challenge: BN_mod failed"); BN_CTX_free(ctx); return challenge; } int -auth_rsa_verify_response(Key *key, BIGNUM *challenge, u_char response[16]) +auth_rsa_verify_response(Key *key, BIGNUM *challenge, u_char response[MAX_HASH_LEN]) { - u_char buf[32], mdbuf[16]; - MD5_CTX md; + u_char buf[2 * MAX_HASH_LEN], mdbuf[MAX_HASH_LEN]; + const EVP_MD *evp_md; + EVP_MD_CTX md; int len, rv; #ifdef SSH_AUDIT_EVENTS char *fp; #endif + int hash_len; /* don't allow short keys */ if (BN_num_bits(key->rsa->n) < SSH_RSA_MINIMUM_MODULUS_SIZE) { error("auth_rsa_verify_response: RSA modulus too small: %d < minimum %d bits", BN_num_bits(key->rsa->n), SSH_RSA_MINIMUM_MODULUS_SIZE); return (0); } - /* The response is MD5 of decrypted challenge plus session id. */ + hash_len = fips_hash_len(fips_hash_min()); + + /* The response is a hash of decrypted challenge plus session id. + * Normally this is MD5, in FIPS mode a stronger function is used. */ len = BN_num_bytes(challenge); - if (len <= 0 || len > 32) + if (len <= 0 || len > (2 * hash_len)) fatal("auth_rsa_verify_response: bad challenge length %d", len); - memset(buf, 0, 32); - BN_bn2bin(challenge, buf + 32 - len); - MD5_Init(&md); - MD5_Update(&md, buf, 32); - MD5_Update(&md, session_id, 16); - MD5_Final(mdbuf, &md); + memset(buf, 0, sizeof(buf)); + BN_bn2bin(challenge, buf + 2 * hash_len - len); + + if ((evp_md = fips_EVP_get_digest_min()) == NULL) { + fatal("auth_rsa_verify_response: fips_EVP_get_digest_min failed"); + } + EVP_DigestInit(&md, evp_md); + EVP_DigestUpdate(&md, buf, 2 * hash_len); + EVP_DigestUpdate(&md, session_id, hash_len); + EVP_DigestFinal(&md, mdbuf, NULL); /* Verify that the response is the original challenge. */ - rv = timingsafe_bcmp(response, mdbuf, 16) == 0; + rv = timingsafe_bcmp(response, mdbuf, hash_len) == 0; #ifdef SSH_AUDIT_EVENTS fp = key_fingerprint(key, key_fp_type_select(), SSH_FP_HEX); if (audit_keyusage(1, "ssh-rsa1", RSA_size(key->rsa) * 8, fp, rv) == 0) { debug("unsuccessful audit"); rv = 0; } free(fp); @@ -135,17 +145,17 @@ auth_rsa_verify_response(Key *key, BIGNU * and returns true (non-zero) if the client gave the correct answer to * our challenge; returns zero if the client gives a wrong answer. */ int auth_rsa_challenge_dialog(Key *key) { BIGNUM *challenge, *encrypted_challenge; - u_char response[16]; + u_char response[MAX_HASH_LEN]; int i, success; if ((encrypted_challenge = BN_new()) == NULL) fatal("auth_rsa_challenge_dialog: BN_new() failed"); challenge = PRIVSEP(auth_rsa_generate_challenge(key)); /* Encrypt the challenge with the public key. */ @@ -155,17 +165,17 @@ auth_rsa_challenge_dialog(Key *key) packet_start(SSH_SMSG_AUTH_RSA_CHALLENGE); packet_put_bignum(encrypted_challenge); packet_send(); BN_clear_free(encrypted_challenge); packet_write_wait(); /* Wait for a response. */ packet_read_expect(SSH_CMSG_AUTH_RSA_RESPONSE); - for (i = 0; i < 16; i++) + for (i = 0; i < fips_hash_len(fips_hash_min()); i++) response[i] = (u_char)packet_get_char(); packet_check_eom(); success = PRIVSEP(auth_rsa_verify_response(key, challenge, response)); BN_clear_free(challenge); return (success); } diff --git a/openssh-6.5p1/cipher-ctr.c b/openssh-6.5p1/cipher-ctr.c --- a/openssh-6.5p1/cipher-ctr.c +++ b/openssh-6.5p1/cipher-ctr.c @@ -21,16 +21,17 @@ #include #include #include #include "xmalloc.h" #include "log.h" +#include "fips.h" /* compatibility with old or broken OpenSSL versions */ #include "openbsd-compat/openssl-compat.h" #ifndef USE_BUILTIN_RIJNDAEL #include #endif @@ -134,13 +135,15 @@ evp_aes_128_ctr(void) aes_ctr.iv_len = AES_BLOCK_SIZE; aes_ctr.key_len = 16; aes_ctr.init = ssh_aes_ctr_init; aes_ctr.cleanup = ssh_aes_ctr_cleanup; aes_ctr.do_cipher = ssh_aes_ctr; #ifndef SSH_OLD_EVP aes_ctr.flags = EVP_CIPH_CBC_MODE | EVP_CIPH_VARIABLE_LENGTH | EVP_CIPH_ALWAYS_CALL_INIT | EVP_CIPH_CUSTOM_IV; + if (fips_mode()) + aes_ctr.flags |= EVP_CIPH_FLAG_FIPS; #endif return (&aes_ctr); } #endif /* OPENSSL_HAVE_EVPCTR */ diff --git a/openssh-6.5p1/cipher.c b/openssh-6.5p1/cipher.c --- a/openssh-6.5p1/cipher.c +++ b/openssh-6.5p1/cipher.c @@ -42,16 +42,17 @@ #include #include #include #include "xmalloc.h" #include "log.h" #include "cipher.h" +#include "fips.h" /* compatibility with old or broken OpenSSL versions */ #include "openbsd-compat/openssl-compat.h" extern const EVP_CIPHER *evp_ssh1_bf(void); extern const EVP_CIPHER *evp_ssh1_3des(void); extern void ssh1_3des_iv(EVP_CIPHER_CTX *, int, u_char *, int); @@ -81,18 +82,57 @@ struct Cipher ciphers[] = { { "aes128-gcm@openssh.com", SSH_CIPHER_SSH2, 16, 16, 12, 16, 0, 0, EVP_aes_128_gcm }, { "aes256-gcm@openssh.com", SSH_CIPHER_SSH2, 16, 32, 12, 16, 0, 0, EVP_aes_256_gcm }, #endif { NULL, SSH_CIPHER_INVALID, 0, 0, 0, 0, 0, 0, NULL } }; +struct Cipher ciphers_fips140_2[] = { + { "none", SSH_CIPHER_NONE, 8, 0, 0, 0, 0, 0, EVP_enc_null }, + { "3des", SSH_CIPHER_3DES, 8, 16, 0, 0, 0, 1, evp_ssh1_3des }, + + { "3des-cbc", SSH_CIPHER_SSH2, 8, 24, 0, 0, 0, 1, EVP_des_ede3_cbc }, + { "aes128-cbc", SSH_CIPHER_SSH2, 16, 16, 0, 0, 0, 1, EVP_aes_128_cbc }, + { "aes192-cbc", SSH_CIPHER_SSH2, 16, 24, 0, 0, 0, 1, EVP_aes_192_cbc }, + { "aes256-cbc", SSH_CIPHER_SSH2, 16, 32, 0, 0, 0, 1, EVP_aes_256_cbc }, + { "rijndael-cbc@lysator.liu.se", + SSH_CIPHER_SSH2, 16, 32, 0, 0, 0, 1, EVP_aes_256_cbc }, + { "aes128-ctr", SSH_CIPHER_SSH2, 16, 16, 0, 0, 0, 0, EVP_aes_128_ctr }, + { "aes192-ctr", SSH_CIPHER_SSH2, 16, 24, 0, 0, 0, 0, EVP_aes_192_ctr }, + { "aes256-ctr", SSH_CIPHER_SSH2, 16, 32, 0, 0, 0, 0, EVP_aes_256_ctr }, +#ifdef OPENSSL_HAVE_EVPGCM + { "aes128-gcm@openssh.com", + SSH_CIPHER_SSH2, 16, 16, 12, 16, 0, 0, EVP_aes_128_gcm }, + { "aes256-gcm@openssh.com", + SSH_CIPHER_SSH2, 16, 32, 12, 16, 0, 0, EVP_aes_256_gcm }, +#endif + { NULL, SSH_CIPHER_INVALID, 0, 0, 0, 0, 0, 0, NULL } +}; + /*--*/ +static struct Cipher * +fips_select_ciphers(void) +{ + int fips = fips_mode(); + switch (fips) { + case 0: + return ciphers; + case 1: + return ciphers_fips140_2; + default: + /* should not be reached */ + fatal("Fatal error: incorrect FIPS mode '%i' at %s:%u", + fips, __FILE__, __LINE__); +// return NULL; + } +} + u_int cipher_blocksize(const Cipher *c) { return (c->block_size); } u_int cipher_keylen(const Cipher *c) @@ -135,27 +175,27 @@ cipher_mask_ssh1(int client) } return mask; } Cipher * cipher_by_name(const char *name) { Cipher *c; - for (c = ciphers; c->name != NULL; c++) + for (c = fips_select_ciphers(); c->name != NULL; c++) if (strcmp(c->name, name) == 0) return c; return NULL; } Cipher * cipher_by_number(int id) { Cipher *c; - for (c = ciphers; c->name != NULL; c++) + for (c = fips_select_ciphers(); c->name != NULL; c++) if (c->number == id) return c; return NULL; } #define CIPHER_SEP "," int ciphers_valid(const char *names) @@ -189,17 +229,17 @@ ciphers_valid(const char *names) */ int cipher_number(const char *name) { Cipher *c; if (name == NULL) return -1; - for (c = ciphers; c->name != NULL; c++) + for (c = fips_select_ciphers(); c->name != NULL; c++) if (strcasecmp(c->name, name) == 0) return c->number; return -1; } char * cipher_name(int id) { @@ -352,24 +392,29 @@ cipher_cleanup(CipherContext *cc) * Selects the cipher, and keys if by computing the MD5 checksum of the * passphrase and using the resulting 16 bytes as the key. */ void cipher_set_key_string(CipherContext *cc, Cipher *cipher, const char *passphrase, int do_encrypt) { - MD5_CTX md; - u_char digest[16]; + const EVP_MD *evp_md; + EVP_MD_CTX md; + u_char digest[MAX_HASH_LEN]; + int dlen; - MD5_Init(&md); - MD5_Update(&md, (const u_char *)passphrase, strlen(passphrase)); - MD5_Final(digest, &md); - - cipher_init(cc, cipher, digest, 16, NULL, 0, do_encrypt); + if ((evp_md = fips_EVP_get_digest_min()) == NULL) { + fatal("auth_rsa_verify_response: fips_EVP_get_digest_min failed"); + } + EVP_DigestInit(&md, evp_md); + EVP_DigestUpdate(&md, (const u_char *)passphrase, strlen(passphrase)); + EVP_DigestFinal(&md, digest, &dlen); + + cipher_init(cc, cipher, digest, dlen, NULL, 0, do_encrypt); memset(digest, 0, sizeof(digest)); memset(&md, 0, sizeof(md)); } /* * Exports an IV from the CipherContext required to export the key * state back from the unprivileged child to the privileged parent diff --git a/openssh-6.5p1/fips.c b/openssh-6.5p1/fips.c new file mode 100644 --- /dev/null +++ b/openssh-6.5p1/fips.c @@ -0,0 +1,176 @@ +/* + * Copyright (c) 2012 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 + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * 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. + */ + +#include "fips.h" + +#include "log.h" + +enum hgp { + HGP_LEN, + HGP_NID +}; + +static int fips_state = -1; + +static const struct Hashes { + enum hash_type ht; + int byte_length; + int nid; +} hashes[] = { + { HASH_MD5, HASH_LEN_MD5, NID_md5 }, + { HASH_SHA1, HASH_LEN_SHA1, NID_sha1 }, + { HASH_SHA256, HASH_LEN_SHA256, NID_sha256 }, + { HASH_err, -1, -1 } +}; + +int +fips_mode() +{ + if (-1 == fips_state) { + fips_state = FIPS_mode(); + if (fips_state) + debug("FIPS mode initialized"); + } + return fips_state; +} + +static int +fips_hash_get_param(enum hash_type ht, int param) +{ + int i; + for (i = 0; i < HASH_err; i++) { + if (hashes[i].ht == ht) { + switch (param) { + case HGP_LEN: + return hashes[i].byte_length; + case HGP_NID: + return hashes[i].nid; + default: + fatal("Fatal error: internal error at %s:%u", + __FILE__, __LINE__); + } + } + } + /* should not be reached */ + fatal("Fatal error: incorrect hash type '%i' at %s:%u", + ht, __FILE__, __LINE__); +// return -1; +} + +int +fips_hash2nid(enum hash_type ht) +{ + return fips_hash_get_param(ht, HGP_NID); +} + +int +fips_hash_len(enum hash_type ht) +{ + return fips_hash_get_param(ht, HGP_LEN); +} + +void +fips_correct_fp_type(enum fp_type *fp) +{ + int fips; + + fips = fips_mode(); + switch (fips) { + case 0: + break; + case 1: + if (SSH_FP_MD5 == *fp) { + *fp = SSH_FP_SHA1; + logit("MD5 not allowed in FIPS 140-2 mode, " + "using SHA-1 for key fingerprints instead."); + } + break; + default: + /* should not be reached */ + fatal("Fatal error: incorrect FIPS mode '%i' at %s:%u", + fips, __FILE__, __LINE__); + } + + return; +} + +void +fips_correct_nid(int *nid) +{ + int fips; + + fips = fips_mode(); + switch (fips) { + case 0: + break; + case 1: + if (NID_md5 == *nid) { + *nid = NID_sha1; + logit("MD5 not allowed in FIPS 140-2 mode, " + "using SHA-1 for hashing instead."); + } + break; + default: + /* should not be reached */ + fatal("Fatal error: incorrect FIPS mode '%i' at %s:%u", + fips, __FILE__, __LINE__); + } + + return; +} + +enum hash_type +fips_hash_min(void) +{ + int fips; + enum hash_type ht; + + fips = fips_mode(); + switch (fips) { + case 0: + ht = HASH_MD5; + break; + case 1: + ht = HASH_SHA1; + break; + default: + /* should not be reached */ + fatal("Fatal error: incorrect FIPS mode '%i' at %s:%u", + fips, __FILE__, __LINE__); + } + return ht; +} + +enum hash_type +fips_hash_nid_min() +{ + return fips_hash2nid(fips_hash_min()); +} + +const EVP_MD * +fips_EVP_get_digest_min(void) +{ + return EVP_get_digestbynid(fips_hash_nid_min()); +} + diff --git a/openssh-6.5p1/fips.h b/openssh-6.5p1/fips.h new file mode 100644 --- /dev/null +++ b/openssh-6.5p1/fips.h @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2012 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 + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * 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 + +#include +#include + +#include "key.h" + +#define HASH_LEN_MD5 16 +#define HASH_LEN_SHA1 20 +#define HASH_LEN_SHA256 32 +#define MAX_HASH_LEN HASH_LEN_SHA256 + +enum hash_type { + HASH_MD5 = 1, + HASH_SHA1, + HASH_SHA256, + HASH_err +}; + +int fips_mode(void); +void fips_correct_fp_type(enum fp_type *); +void fips_correct_nid(int *); + +enum hash_type fips_hash_min(void); +const EVP_MD *fips_EVP_get_digest_min(void); +int fips_hash2nid(enum hash_type); +int fips_hash_len(enum hash_type); + +#endif + diff --git a/openssh-6.5p1/key.c b/openssh-6.5p1/key.c --- a/openssh-6.5p1/key.c +++ b/openssh-6.5p1/key.c @@ -49,16 +49,17 @@ #include "xmalloc.h" #include "key.h" #include "rsa.h" #include "uuencode.h" #include "buffer.h" #include "log.h" #include "misc.h" #include "ssh2.h" +#include "fips.h" static int to_blob(const Key *, u_char **, u_int *, int); static struct KeyCert * cert_new(void) { struct KeyCert *cert; @@ -634,16 +635,19 @@ key_fp_type_select(void) error("invalid key type in environment variable " SSH_FP_TYPE_ENVVAR ": '%s' - falling back to MD5.", env); fp = SSH_FP_MD5; } } else fp = SSH_FP_MD5; + if (fips_mode()) + fips_correct_fp_type(&fp); + fp_defined = 1; } return fp; } /* * string lengths must be less or equal to SSH_FP_TYPE_STRLEN (defined in * key.h) as to fit into the fingerprint string buffer diff --git a/openssh-6.5p1/mac.c b/openssh-6.5p1/mac.c --- a/openssh-6.5p1/mac.c +++ b/openssh-6.5p1/mac.c @@ -36,34 +36,35 @@ #include "xmalloc.h" #include "log.h" #include "cipher.h" #include "buffer.h" #include "key.h" #include "kex.h" #include "mac.h" #include "misc.h" +#include "fips.h" #include "umac.h" #include "openbsd-compat/openssl-compat.h" #define SSH_EVP 1 /* OpenSSL EVP-based MAC */ #define SSH_UMAC 2 /* UMAC (not integrated with OpenSSL) */ #define SSH_UMAC128 3 -struct { +struct Macs { char *name; int type; const EVP_MD * (*mdfunc)(void); int truncatebits; /* truncate digest if != 0 */ int key_len; /* just for UMAC */ int len; /* just for UMAC */ int etm; /* Encrypt-then-MAC */ -} macs[] = { +} macs_all[] = { /* Encrypt-and-MAC (encrypt-and-authenticate) variants */ { "hmac-sha1", SSH_EVP, EVP_sha1, 0, 0, 0, 0 }, { "hmac-sha1-96", SSH_EVP, EVP_sha1, 96, 0, 0, 0 }, #ifdef HAVE_EVP_SHA256 { "hmac-sha2-256", SSH_EVP, EVP_sha256, 0, 0, 0, 0 }, { "hmac-sha2-512", SSH_EVP, EVP_sha512, 0, 0, 0, 0 }, #endif { "hmac-md5", SSH_EVP, EVP_md5, 0, 0, 0, 0 }, @@ -84,19 +85,46 @@ struct { { "hmac-md5-96-etm@openssh.com", SSH_EVP, EVP_md5, 96, 0, 0, 1 }, { "hmac-ripemd160-etm@openssh.com", SSH_EVP, EVP_ripemd160, 0, 0, 0, 1 }, { "umac-64-etm@openssh.com", SSH_UMAC, NULL, 0, 128, 64, 1 }, { "umac-128-etm@openssh.com", SSH_UMAC128, NULL, 0, 128, 128, 1 }, { NULL, 0, NULL, 0, 0, 0, 0 } }; +struct Macs macs_fips140_2[] = { + { "hmac-sha1", SSH_EVP, EVP_sha1, 0, 0, 0, 0 }, +#ifdef HAVE_EVP_SHA256 + { "hmac-sha2-256", SSH_EVP, EVP_sha256, 0, 0, 0, 0 }, + { "hmac-sha2-512", SSH_EVP, EVP_sha512, 0, 0, 0, 0 }, +#endif + { NULL, 0, NULL, 0, 0, 0, 0 } +}; + +static struct Macs * +fips_select_macs(void) +{ + int fips = fips_mode(); + switch (fips) { + case 0: + return macs_all; + case 1: + return macs_fips140_2; + default: + /* should not be reached */ + fatal("Fatal error: incorrect FIPS mode '%i' at %s:%u", + fips, __FILE__, __LINE__); +// return NULL; + } +} + static void mac_setup_by_id(Mac *mac, int which) { + struct Macs *macs = fips_select_macs(); int evp_len; mac->type = macs[which].type; if (mac->type == SSH_EVP) { mac->evp_md = (*macs[which].mdfunc)(); if ((evp_len = EVP_MD_size(mac->evp_md)) <= 0) fatal("mac %s len %d", mac->name, evp_len); mac->key_len = mac->mac_len = (u_int)evp_len; } else { @@ -107,16 +135,17 @@ mac_setup_by_id(Mac *mac, int which) if (macs[which].truncatebits != 0) mac->mac_len = macs[which].truncatebits / 8; mac->etm = macs[which].etm; } int mac_setup(Mac *mac, char *name) { + struct Macs *macs = fips_select_macs(); int i; for (i = 0; macs[i].name; i++) { if (strcmp(name, macs[i].name) == 0) { if (mac != NULL) mac_setup_by_id(mac, i); debug2("mac_setup: found %s", name); return (0); diff --git a/openssh-6.5p1/myproposal.h b/openssh-6.5p1/myproposal.h --- a/openssh-6.5p1/myproposal.h +++ b/openssh-6.5p1/myproposal.h @@ -71,16 +71,20 @@ "ssh-dss" #define KEX_DEFAULT_ENCRYPT \ "aes128-ctr,aes192-ctr,aes256-ctr," \ "arcfour256,arcfour128," \ "aes128-gcm@openssh.com,aes256-gcm@openssh.com," \ "aes128-cbc,3des-cbc,blowfish-cbc,cast128-cbc," \ "aes192-cbc,aes256-cbc,arcfour,rijndael-cbc@lysator.liu.se" +#define KEX_FIPS_140_2_ENCRYPT \ + "aes128-ctr,aes192-ctr,aes256-ctr," \ + "aes128-cbc,3des-cbc," \ + "aes192-cbc,aes256-cbc,rijndael-cbc@lysator.liu.se" #ifdef HAVE_EVP_SHA256 #define SHA2_HMAC_MODES \ "hmac-sha2-256," \ "hmac-sha2-512," #else # define SHA2_HMAC_MODES #endif #define KEX_DEFAULT_MAC \ @@ -97,16 +101,19 @@ "hmac-sha1," \ "umac-64@openssh.com," \ "umac-128@openssh.com," \ SHA2_HMAC_MODES \ "hmac-ripemd160," \ "hmac-ripemd160@openssh.com," \ "hmac-sha1-96," \ "hmac-md5-96" +#define KEX_FIPS_140_2_MAC \ + "hmac-sha1," \ + SHA2_HMAC_MODES \ #define KEX_DEFAULT_COMP "none,zlib@openssh.com,zlib" #define KEX_DEFAULT_LANG "" static char *myproposal[PROPOSAL_MAX] = { KEX_DEFAULT_KEX, KEX_DEFAULT_PK_ALG, diff --git a/openssh-6.5p1/openbsd-compat/bsd-arc4random.c b/openssh-6.5p1/openbsd-compat/bsd-arc4random.c --- a/openssh-6.5p1/openbsd-compat/bsd-arc4random.c +++ b/openssh-6.5p1/openbsd-compat/bsd-arc4random.c @@ -18,34 +18,35 @@ #include #include #include #include #include "log.h" +#include "fips.h" #ifndef HAVE_ARC4RANDOM #include #include #include /* Size of key to use */ #define SEED_SIZE 20 /* Number of bytes to reseed after */ #define REKEY_BYTES (1 << 24) static int rc4_ready = 0; static RC4_KEY rc4; -unsigned int -arc4random(void) +static unsigned int +arc4random_bsd(void) { unsigned int r = 0; static int first_time = 1; if (rc4_ready <= 0) { if (first_time) seed_rng(); first_time = 0; @@ -54,18 +55,18 @@ arc4random(void) RC4(&rc4, sizeof(r), (unsigned char *)&r, (unsigned char *)&r); rc4_ready -= sizeof(r); return(r); } -void -arc4random_stir(void) +static void +arc4random_stir_bsd(void) { unsigned char rand_buf[SEED_SIZE]; int i; memset(&rc4, 0, sizeof(rc4)); if (RAND_bytes(rand_buf, sizeof(rand_buf)) <= 0) fatal("Couldn't obtain random bytes (error %ld)", ERR_get_error()); @@ -77,16 +78,63 @@ arc4random_stir(void) */ for(i = 0; i <= 256; i += sizeof(rand_buf)) RC4(&rc4, sizeof(rand_buf), rand_buf, rand_buf); memset(rand_buf, 0, sizeof(rand_buf)); rc4_ready = REKEY_BYTES; } + +static unsigned int +arc4random_fips(void) +{ + unsigned int r = 0; + void *rp = &r; + static int first_time = 1; + + if (first_time) { + seed_rng(); + first_time = 0; + } + if (!rc4_ready) + arc4random_stir(); + RAND_bytes(rp, sizeof(r)); + + return(r); +} + +static void +arc4random_stir_fips(void) +{ + unsigned char rand_buf[SEED_SIZE]; + + if (RAND_bytes(rand_buf, sizeof(rand_buf)) <= 0) + fatal("Couldn't obtain random bytes (error %ld)", + ERR_get_error()); + rc4_ready = 1; +} + +unsigned int +arc4random(void) +{ + if (fips_mode()) + return arc4random_fips(); + else + return arc4random_bsd(); +} + +void +arc4random_stir(void) +{ + if (fips_mode()) + return arc4random_stir_fips(); + else + return arc4random_stir_bsd(); +} #endif /* !HAVE_ARC4RANDOM */ #ifndef HAVE_ARC4RANDOM_BUF void arc4random_buf(void *_buf, size_t n) { size_t i; u_int32_t r = 0; diff --git a/openssh-6.5p1/ssh-rsa.c b/openssh-6.5p1/ssh-rsa.c --- a/openssh-6.5p1/ssh-rsa.c +++ b/openssh-6.5p1/ssh-rsa.c @@ -27,16 +27,17 @@ #include "xmalloc.h" #include "log.h" #include "buffer.h" #include "key.h" #include "compat.h" #include "misc.h" #include "ssh.h" +#include "fips.h" static int openssh_RSA_verify(int, u_char *, u_int, u_char *, u_int, RSA *); /* RSASSA-PKCS1-v1_5 (PKCS #1 v2.0 signature) with SHA1 */ int ssh_rsa_sign(const Key *key, u_char **sigp, u_int *lenp, const u_char *data, u_int datalen) { @@ -48,16 +49,17 @@ ssh_rsa_sign(const Key *key, u_char **si Buffer b; if (key == NULL || key->rsa == NULL || (key->type != KEY_RSA && key->type != KEY_RSA_CERT && key->type != KEY_RSA_CERT_V00)) { error("ssh_rsa_sign: no RSA key"); return -1; } nid = (datafellows & SSH_BUG_RSASIGMD5) ? NID_md5 : NID_sha1; + fips_correct_nid(&nid); if ((evp_md = EVP_get_digestbynid(nid)) == NULL) { error("ssh_rsa_sign: EVP_get_digestbynid %d failed", nid); return -1; } EVP_DigestInit(&md, evp_md); EVP_DigestUpdate(&md, data, datalen); EVP_DigestFinal(&md, digest, &dlen); @@ -154,16 +156,17 @@ ssh_rsa_verify(const Key *key, const u_c debug("ssh_rsa_verify: add padding: modlen %u > len %u", modlen, len); sigblob = xrealloc(sigblob, 1, modlen); memmove(sigblob + diff, sigblob, len); memset(sigblob, 0, diff); len = modlen; } nid = (datafellows & SSH_BUG_RSASIGMD5) ? NID_md5 : NID_sha1; + fips_correct_nid(&nid); if ((evp_md = EVP_get_digestbynid(nid)) == NULL) { error("ssh_rsa_verify: EVP_get_digestbynid %d failed", nid); free(sigblob); return -1; } EVP_DigestInit(&md, evp_md); EVP_DigestUpdate(&md, data, datalen); EVP_DigestFinal(&md, digest, &dlen); diff --git a/openssh-6.5p1/ssh.c b/openssh-6.5p1/ssh.c --- a/openssh-6.5p1/ssh.c +++ b/openssh-6.5p1/ssh.c @@ -99,16 +99,17 @@ #include "kex.h" #include "mac.h" #include "sshpty.h" #include "match.h" #include "msg.h" #include "uidswap.h" #include "roaming.h" #include "version.h" +#include "fips.h" #ifdef ENABLE_PKCS11 #include "ssh-pkcs11.h" #endif extern char *__progname; /* Saves a copy of argv for setproctitle emulation */ @@ -324,16 +325,18 @@ main(int ac, char **av) use_syslog = 0; argv0 = av[0]; again: while ((opt = getopt(ac, av, "1246ab:c:e:fgi:kl:m:no:p:qstvx" "ACD:F:I:KL:MNO:PR:S:TVw:W:XYy")) != -1) { switch (opt) { case '1': + if (fips_mode()) + fatal("Protocol 1 not allowed in the FIPS mode."); options.protocol = SSH_PROTO_1; break; case '2': options.protocol = SSH_PROTO_2; break; case '4': options.address_family = AF_INET; break; @@ -781,17 +784,22 @@ main(int ac, char **av) free(cp); } if (muxclient_command != 0 && options.control_path == NULL) fatal("No ControlPath specified for \"-O\" command"); if (options.control_path != NULL) muxclient(options.control_path); timeout_ms = options.connection_timeout * 1000; - + if (FIPS_mode()) { + options.protocol &= SSH_PROTO_2; + if (options.protocol == 0) + fatal("Protocol 2 disabled by configuration but required in the FIPS mode"); + } + /* Open a connection to the remote host. */ if (ssh_connect(host, &hostaddr, options.port, options.address_family, options.connection_attempts, &timeout_ms, options.tcp_keep_alive, #ifdef HAVE_CYGWIN options.use_privileged_port, #else original_effective_uid == 0 && options.use_privileged_port, diff --git a/openssh-6.5p1/sshconnect2.c b/openssh-6.5p1/sshconnect2.c --- a/openssh-6.5p1/sshconnect2.c +++ b/openssh-6.5p1/sshconnect2.c @@ -67,16 +67,17 @@ #include "dispatch.h" #include "canohost.h" #include "msg.h" #include "pathnames.h" #include "uidswap.h" #include "hostfile.h" #include "schnorr.h" #include "jpake.h" +#include "fips.h" #ifdef GSSAPI #include "ssh-gss.h" #endif /* import */ extern char *client_version_string; extern char *server_version_string; @@ -165,31 +166,37 @@ ssh_kex2(char *host, struct sockaddr *ho if (options.ciphers == (char *)-1) { logit("No valid ciphers for protocol version 2 given, using defaults."); options.ciphers = NULL; } if (options.ciphers != NULL) { myproposal[PROPOSAL_ENC_ALGS_CTOS] = myproposal[PROPOSAL_ENC_ALGS_STOC] = options.ciphers; + } else if (fips_mode()) { + myproposal[PROPOSAL_ENC_ALGS_CTOS] = + myproposal[PROPOSAL_ENC_ALGS_STOC] = KEX_FIPS_140_2_ENCRYPT; } myproposal[PROPOSAL_ENC_ALGS_CTOS] = compat_cipher_proposal(myproposal[PROPOSAL_ENC_ALGS_CTOS]); myproposal[PROPOSAL_ENC_ALGS_STOC] = compat_cipher_proposal(myproposal[PROPOSAL_ENC_ALGS_STOC]); if (options.compression) { myproposal[PROPOSAL_COMP_ALGS_CTOS] = myproposal[PROPOSAL_COMP_ALGS_STOC] = "zlib@openssh.com,zlib,none"; } else { myproposal[PROPOSAL_COMP_ALGS_CTOS] = myproposal[PROPOSAL_COMP_ALGS_STOC] = "none,zlib@openssh.com,zlib"; } if (options.macs != NULL) { myproposal[PROPOSAL_MAC_ALGS_CTOS] = myproposal[PROPOSAL_MAC_ALGS_STOC] = options.macs; + } else if (fips_mode()) { + myproposal[PROPOSAL_MAC_ALGS_CTOS] = + myproposal[PROPOSAL_MAC_ALGS_STOC] = KEX_FIPS_140_2_MAC; } if (options.hostkeyalgorithms != NULL) myproposal[PROPOSAL_SERVER_HOST_KEY_ALGS] = options.hostkeyalgorithms; else { /* Prefer algorithms that we already have keys for */ myproposal[PROPOSAL_SERVER_HOST_KEY_ALGS] = order_hostkeyalgs(host, hostaddr, port); diff --git a/openssh-6.5p1/sshd.c b/openssh-6.5p1/sshd.c --- a/openssh-6.5p1/sshd.c +++ b/openssh-6.5p1/sshd.c @@ -118,16 +118,17 @@ #ifdef GSSAPI #include "ssh-gss.h" #endif #include "monitor_wrap.h" #include "roaming.h" #include "audit.h" #include "ssh-sandbox.h" #include "version.h" +#include "fips.h" #ifdef LIBWRAP #include #include int allow_severity; int deny_severity; #endif /* LIBWRAP */ @@ -1711,16 +1712,20 @@ main(int ac, char **av) case KEY_DSA: case KEY_ECDSA: sensitive_data.have_ssh2_key = 1; break; } debug("private host key: #%d type %d %s", i, key->type, key_type(key)); } + if ((options.protocol & SSH_PROTO_1) && fips_mode()) { + logit("Disabling protocol version 1. Not allowed in the FIPS mode."); + options.protocol &= ~SSH_PROTO_1; + } if ((options.protocol & SSH_PROTO_1) && !sensitive_data.have_ssh1_key) { logit("Disabling protocol version 1. Could not load host key"); options.protocol &= ~SSH_PROTO_1; } if ((options.protocol & SSH_PROTO_2) && !sensitive_data.have_ssh2_key) { logit("Disabling protocol version 2. Could not load host key"); options.protocol &= ~SSH_PROTO_2; } @@ -2413,25 +2418,31 @@ do_ssh1_kex(void) static void do_ssh2_kex(void) { Kex *kex; if (options.ciphers != NULL) { myproposal[PROPOSAL_ENC_ALGS_CTOS] = myproposal[PROPOSAL_ENC_ALGS_STOC] = options.ciphers; + } else if (fips_mode()) { + myproposal[PROPOSAL_ENC_ALGS_CTOS] = + myproposal[PROPOSAL_ENC_ALGS_STOC] = KEX_FIPS_140_2_ENCRYPT; } myproposal[PROPOSAL_ENC_ALGS_CTOS] = compat_cipher_proposal(myproposal[PROPOSAL_ENC_ALGS_CTOS]); myproposal[PROPOSAL_ENC_ALGS_STOC] = compat_cipher_proposal(myproposal[PROPOSAL_ENC_ALGS_STOC]); if (options.macs != NULL) { myproposal[PROPOSAL_MAC_ALGS_CTOS] = myproposal[PROPOSAL_MAC_ALGS_STOC] = options.macs; + } else if (fips_mode()) { + myproposal[PROPOSAL_MAC_ALGS_CTOS] = + myproposal[PROPOSAL_MAC_ALGS_STOC] = KEX_FIPS_140_2_MAC; } if (options.compression == COMP_NONE) { myproposal[PROPOSAL_COMP_ALGS_CTOS] = myproposal[PROPOSAL_COMP_ALGS_STOC] = "none"; } else if (options.compression == COMP_DELAYED) { myproposal[PROPOSAL_COMP_ALGS_CTOS] = myproposal[PROPOSAL_COMP_ALGS_STOC] = "none,zlib@openssh.com"; }