Hi,
Attached is a patch based off of 79d51099ac3273aa73c3bd3bd047b3fa996fef18 on
the master branch which should resolve https://red.libssh.org/issues/159.
Before the patch, I'm able to reproduce failure when testing with OpenSSH
clients at or beyond 6.5p1, using the 'pkd_hello' test like so:
./pkd_hello -t torture_pkd_openssh_rsa_curve25519_sha256 -i 1000
After the patch I'm unable to reproduce the failure.
Thanks,
-Jon
>From 689fa761b4f0968dff511af638c411ef6def14c6 Mon Sep 17 00:00:00 2001
From: Jon Simons <[email protected]>
Date: Mon, 14 Apr 2014 20:04:00 -0700
Subject: [PATCH] dh: fix [email protected] 'K' padding
Ensure that bignum strings used for the shared-secret 'K' value in
key exchange with [email protected] are indeed 32 bytes.
Before this change, OpenSSH clients starting at version 6.5p1 will
sometimes, but not always, error out in the early key exchange
phase along the lines of:
debug2: kex_parse_kexinit:
[email protected],ecdh-sha2-nistp256,diffie-hellman-group14-sha1,diffie-hellman-group1-sha1
...
debug1: sending SSH2_MSG_KEX_ECDH_INIT
debug1: expecting SSH2_MSG_KEX_ECDH_REPLY
...
Warning: Permanently added '[localhost]:1234' (RSA) to the list of known
hosts.
hash mismatch
debug1: ssh_rsa_verify: signature incorrect
key_verify failed for server_host_key
After some digging it looks like there are some cases where the
ssh_string computed by libssh for its 'K' value does not have a
length of 32 bytes. It is in these cases that OpenSSH clients
will fail signature verification during initial key exchange.
To fix, use a new helper for generating 'K' strings which inputs
both a bignum and minimal size. For the curve25519 case, the 32
byte value is provided for the size; other key exchange algorithms
continue to use plain 'make_bignum_string' as before.
With this change I am no longer able to reproduce failure when
testing against OpenSSH clients.
BUG: https://red.libssh.org/issues/159
Signed-off-by: Jon Simons <[email protected]>
---
include/libssh/curve25519.h | 6 ++-
src/dh.c | 104 ++++++++++++++++++++++++++++++++------------
2 files changed, 79 insertions(+), 31 deletions(-)
diff --git a/include/libssh/curve25519.h b/include/libssh/curve25519.h
index 0406b9e..1f8b915 100644
--- a/include/libssh/curve25519.h
+++ b/include/libssh/curve25519.h
@@ -27,14 +27,16 @@
#ifdef WITH_NACL
#include <nacl/crypto_scalarmult_curve25519.h>
+#define CURVE25519_SIZE 32
#define CURVE25519_PUBKEY_SIZE crypto_scalarmult_curve25519_BYTES
#define CURVE25519_PRIVKEY_SIZE crypto_scalarmult_curve25519_SCALARBYTES
#define crypto_scalarmult_base crypto_scalarmult_curve25519_base
#define crypto_scalarmult crypto_scalarmult_curve25519
#else
-#define CURVE25519_PUBKEY_SIZE 32
-#define CURVE25519_PRIVKEY_SIZE 32
+#define CURVE25519_SIZE 32
+#define CURVE25519_PUBKEY_SIZE CURVE25519_SIZE
+#define CURVE25519_PRIVKEY_SIZE CURVE25519_SIZE
int crypto_scalarmult_base(unsigned char *q, const unsigned char *n);
int crypto_scalarmult(unsigned char *q, const unsigned char *n, const unsigned
char *p);
#endif /* WITH_NACL */
diff --git a/src/dh.c b/src/dh.c
index 3255ac7..be04db5 100644
--- a/src/dh.c
+++ b/src/dh.c
@@ -351,42 +351,59 @@ int dh_generate_f(ssh_session session) {
return 0;
}
-ssh_string make_bignum_string(bignum num) {
- ssh_string ptr = NULL;
- int pad = 0;
- unsigned int len = bignum_num_bytes(num);
- unsigned int bits = bignum_num_bits(num);
-
- if (len == 0) {
- return NULL;
- }
+/**
+ * @internal
+ * @brief return a bignum string of at least the given size
+ *
+ * Returns a bignum string of at least the given size, prepending
+ * zeroes as necessary. A given size of zero will be ignored.
+ *
+ * @param num the bignum source for the resulting string
+ * @param min_size size in bytes of result, or zero for no minimum size
+ *
+ * @returns an ssh_string of at least the given size
+ */
+static ssh_string make_sized_bignum_string(bignum num, unsigned int min_size) {
+ ssh_string ptr = NULL;
+ unsigned int p = 0;
+ unsigned int pad = 0;
+ unsigned int len = bignum_num_bytes(num);
+ unsigned int bits = bignum_num_bits(num);
+
+ if (len == 0) {
+ return NULL;
+ }
- /* If the first bit is set we have a negative number */
- if (!(bits % 8) && bignum_is_bit_set(num, bits - 1)) {
- pad++;
- }
+ /* Prepend zero if MSB is set (negative number). */
+ if (!(bits % 8) && bignum_is_bit_set(num, bits - 1)) {
+ pad += 1;
+ }
-#ifdef DEBUG_CRYPTO
- fprintf(stderr, "%d bits, %d bytes, %d padding\n", bits, len, pad);
-#endif /* DEBUG_CRYPTO */
+ /* Ensure padding adds up to min_size. */
+ if ((min_size > 0) && ((len + pad) < min_size)) {
+ pad += (min_size - (len + pad));
+ }
- ptr = ssh_string_new(len + pad);
- if (ptr == NULL) {
- return NULL;
- }
+ ptr = ssh_string_new(len + pad);
+ if (ptr == NULL) {
+ return NULL;
+ }
- /* We have a negative number so we need a leading zero */
- if (pad) {
- ptr->data[0] = 0;
- }
+ while (p < pad) {
+ ptr->data[p++] = 0;
+ }
#ifdef HAVE_LIBGCRYPT
- bignum_bn2bin(num, len, ptr->data + pad);
+ bignum_bn2bin(num, len, ptr->data + pad);
#elif HAVE_LIBCRYPTO
- bignum_bn2bin(num, ptr->data + pad);
+ bignum_bn2bin(num, ptr->data + pad);
#endif
- return ptr;
+ return ptr;
+}
+
+ssh_string make_bignum_string(bignum num) {
+ return make_sized_bignum_string(num, 0);
}
bignum make_string_bn(ssh_string string){
@@ -416,6 +433,34 @@ ssh_string dh_get_f(ssh_session session) {
return make_bignum_string(session->next_crypto->f);
}
+/**
+ * @internal
+ * @brief return given session's shared secret 'K' as an ssh_string
+ * @param session the session whose 'K' value to retrieve
+ * @returns an ssh_string for the given session's shared secret 'K'
+ * @returns NULL upon error
+ */
+static ssh_string dh_get_k(ssh_session session) {
+ ssh_string k_string = NULL;
+ bignum k = session->next_crypto->k;
+ enum ssh_key_exchange_e kex_type = session->next_crypto->kex_type;
+
+ switch (kex_type) {
+ case SSH_KEX_DH_GROUP1_SHA1:
+ case SSH_KEX_DH_GROUP14_SHA1:
+ case SSH_KEX_ECDH_SHA2_NISTP256:
+ k_string = make_bignum_string(k);
+ break;
+ case SSH_KEX_CURVE25519_SHA256_LIBSSH_ORG:
+ k_string = make_sized_bignum_string(k, CURVE25519_SIZE);
+ break;
+ default:
+ break;
+ }
+
+ return k_string;
+}
+
void dh_import_pubkey(ssh_session session, ssh_string pubkey_string) {
session->next_crypto->server_pubkey = pubkey_string;
}
@@ -742,7 +787,8 @@ int make_sessionid(ssh_session session) {
#endif
}
- num = make_bignum_string(session->next_crypto->k);
+ /* Build shared secret 'K' according to session's kex type. */
+ num = dh_get_k(session);
if (num == NULL) {
goto error;
}
@@ -888,7 +934,7 @@ int generate_session_keys(ssh_session session) {
unsigned char *tmp;
int rc = -1;
- k_string = make_bignum_string(crypto->k);
+ k_string = dh_get_k(session);
if (k_string == NULL) {
ssh_set_error_oom(session);
goto error;
--
1.8.4.21.g992c386