SABER Level 1 Support in wolfSSH

pull/408/head
Anthony Hu 2022-03-24 14:17:35 -04:00
parent 8a714b2864
commit cdde29da65
4 changed files with 271 additions and 16 deletions

View File

@ -13,7 +13,7 @@ wolfSSL. The following is the simplest configuration of wolfSSL to
enable wolfSSH.
$ cd wolfssl
$ ./configure [OPTIONS] --enable-ssh
$ ./configure [OPTIONS] --enable-wolfssh
$ make check
$ sudo make install
@ -398,3 +398,64 @@ By default, the echoserver will try to start a shell. To use the echo testing
behavior, give the echoserver the command line option `-f`.
$ ./examples/echoserver/echoserver -f
POST-QUANTUM
============
wolfSSH now supports the post-quantum algorithm SABER. It uses the NIST
submission's Level 1 parameter set implemented by liboqs via an integration
with wolfSSH.
In order be able to use liboqs, you must have it built and installed on your
system. We support the 0.7.0 release of liboqs. You can download it from the
following link:
https://github.com/open-quantum-safe/liboqs/archive/refs/tags/0.7.0.tar.gz
Once unpacked, this would be sufficient:
$ cd liboqs-0.7.0
$ mkdir build
$ cd build
$ cmake -DOQS_USE_OPENSSL=0 ..
$ make all
$ sudo make install
In order to enable support for SABER Level1 in wolfSSH, use the `--with-liboqs`
build option during configuration:
$ ./configure --with-liboqs
The wolfSSH client and server will automatically negotiate using SABER Level1
if this feature is enabled.
$ ./examples/echoserver/echoserver -f
$ ./examples/client/client -u jill -P upthehill
On the client side, you will see the following output:
Server said: Hello, wolfSSH!
If you want to see inter-operability with OpenQauntumSafe's fork of OpenSSH, you
can build and execute the fork while the echoserver is running. Download the
release from here:
https://github.com/open-quantum-safe/openssh/archive/refs/tags/OQS-OpenSSH-snapshot-2021-08.tar.gz
The following is sufficient for build and execution:
$ tar xmvf openssh-OQS-OpenSSH-snapshot-2021-08.tar.gz
$ cd openssh-OQS-OpenSSH-snapshot-2021-08/
$ ./configure --with-liboqs-dir=/usr/local
$ make all
$ ./ssh -o"KexAlgorithms +saber-lightsaber-sha256" \
-o"PubkeyAcceptedAlgorithms +ssh-rsa" \
-o"HostkeyAlgorithms +ssh-rsa" \
jill@localhost -p 22222
NOTE: when prompted, enter the password which is "upthehill".
You can type a line of text and when you press enter, the line will be echoed
back. Use CTRL-C to terminate the connection.

View File

@ -56,6 +56,46 @@ AC_CHECK_HEADERS([sys/select.h sys/time.h pty.h util.h termios.h])
AC_CHECK_LIB([network],[socket])
AC_CHECK_LIB([util],[forkpty])
# liboqs
ENABLED_LIBOQS="no"
tryliboqsdir=""
AC_ARG_WITH([liboqs],
[AS_HELP_STRING([--with-liboqs=PATH],[Path to liboqs install (default /usr/local) EXPERIMENTAL!])],
[
AC_MSG_CHECKING([for liboqs])
CPPFLAGS="$CPPFLAGS -DWOLFSSH_HAVE_LIBOQS"
LIBS="$LIBS -loqs"
AC_LINK_IFELSE([AC_LANG_PROGRAM([[#include <oqs/common.h>]], [[ OQS_init(); ]])], [ liboqs_linked=yes ],[ liboqs_linked=no ])
if test "x$liboqs_linked" = "xno" ; then
if test "x$withval" != "xno" ; then
tryliboqsdir=$withval
fi
if test "x$withval" = "xyes" ; then
tryliboqsdir="/usr/local"
fi
LDFLAGS="$AM_LDFLAGS $LDFLAGS -L$tryliboqsdir/lib"
CPPFLAGS="$CPPFLAGS -I$tryliboqsdir/include"
AC_LINK_IFELSE([AC_LANG_PROGRAM([[#include <oqs/common.h>]], [[ OQS_init(); ]])], [ liboqs_linked=yes ],[ liboqs_linked=no ])
if test "x$liboqs_linked" = "xno" ; then
AC_MSG_ERROR([liboqs isn't found.
If it's already installed, specify its path using --with-liboqs=/dir/])
fi
AC_MSG_RESULT([yes])
AM_LDFLAGS="$AM_LDFLAGS -L$tryliboqsdir/lib"
else
AC_MSG_RESULT([yes])
fi
AM_CFLAGS="$AM_CFLAGS -DWOLFSSH_HAVE_LIBOQS"
ENABLED_LIBOQS="yes"
]
)
#wolfssl
AC_MSG_CHECKING([for wolfSSL])
if test "x$prefix" = "xNONE"
@ -245,3 +285,4 @@ AS_ECHO([" * sftp: $ENABLED_SFTP"])
AS_ECHO([" * agent: $ENABLED_AGENT"])
AS_ECHO([" * TCP/IP Forwarding: $ENABLED_FWD"])
AS_ECHO([" * Examples: $ENABLED_EXAMPLES"])
AS_ECHO([" * liboqs Integration: $ENABLED_LIBOQS"])

View File

@ -42,6 +42,10 @@
#include <wolfssl/wolfcrypt/integer.h>
#include <wolfssl/wolfcrypt/signature.h>
#ifdef WOLFSSH_HAVE_LIBOQS
#include <oqs/kem.h>
#endif
#ifdef NO_INLINE
#include <wolfssh/misc.h>
#else
@ -49,7 +53,6 @@
#include "src/misc.c"
#endif
/*
Flags:
HAVE_WC_ECC_SET_RNG
@ -103,6 +106,9 @@ Flags:
WOLFSSH_NO_ECDSA_SHA2_NISTP521
Set when ECC or SHA2-512 are disabled. Set to disable use of ECDSA server
authentication with prime NISTP521.
WOLFSSH_NO_SABER_LEVEL1_SHA256
Set when there is no liboqs integration. Set to disable use of post-quantum
SABER Level 1 KEM.
WOLFSSH_NO_AES_CBC
Set when AES or AES-CBC are disabled. Set to disable use of AES-CBC
encryption.
@ -1091,7 +1097,10 @@ static const NameIdPair NameIdMap[] = {
#ifndef WOLFSSH_NO_DH_GEX_SHA256
{ ID_DH_GROUP14_SHA256, "diffie-hellman-group14-sha256" },
#endif
#ifndef WOLFSSH_NO_SABER_LEVEL1_SHA256
/* We use lightsaber here to achieve interop with OQS's fork. */
{ ID_SABER_LEVEL1_SHA256, "saber-lightsaber-sha256" },
#endif
/* Public Key IDs */
#ifndef WOLFSSH_NO_SSH_RSA_SHA1
{ ID_SSH_RSA, "ssh-rsa" },
@ -2076,6 +2085,9 @@ static const word32 cannedKeyAlgoEcc521Sz = sizeof(cannedKeyAlgoEcc521);
static const byte cannedKexAlgo[] = {
#ifndef WOLFSSH_NO_SABER_LEVEL1_SHA256
ID_SABER_LEVEL1_SHA256,
#endif
#ifndef WOLFSSH_NO_ECDH_SHA2_NISTP521
ID_ECDH_SHA2_NISTP521,
#endif
@ -2260,6 +2272,10 @@ static INLINE enum wc_HashType HashForId(byte id)
case ID_ECDSA_SHA2_NISTP256:
return WC_HASH_TYPE_SHA256;
#endif
#ifndef WOLFSSH_NO_SABER_LEVEL1_SHA256
case ID_SABER_LEVEL1_SHA256:
return WC_HASH_TYPE_SHA256;
#endif
/* SHA2-384 */
#ifndef WOLFSSH_NO_ECDH_SHA2_NISTP384
@ -3246,7 +3262,7 @@ static int DoKexDhReply(WOLFSSH* ssh, byte* buf, word32 len, word32* idx)
* potentially be smaller by a byte than usual and cause buffer
* issues with re-key */
ssh->kSz = MAX_KEX_KEY_SZ;
if (!ssh->handshake->useEcc) {
if ((!ssh->handshake->useEcc) && (!ssh->handshake->useSaber)) {
#ifndef WOLFSSH_NO_DH
#ifdef PRIVATE_KEY_UNLOCK
PRIVATE_KEY_UNLOCK();
@ -3268,7 +3284,7 @@ static int DoKexDhReply(WOLFSSH* ssh, byte* buf, word32 len, word32* idx)
ret = WS_INVALID_ALGO_ID;
#endif
}
else {
else if (ssh->handshake->useEcc) {
#ifndef WOLFSSH_NO_ECDH
ret = wc_ecc_init(key_ptr);
#ifdef HAVE_WC_ECC_SET_RNG
@ -3296,6 +3312,39 @@ static int DoKexDhReply(WOLFSSH* ssh, byte* buf, word32 len, word32* idx)
#else
ret = WS_INVALID_ALGO_ID;
#endif
} else if (ssh->handshake->useSaber) {
#ifndef WOLFSSH_NO_SABER_LEVEL1_SHA256
/* This is a KEM. In this case, I need to decapsulate the
* ciphertext. */
OQS_KEM* kem = NULL;
ret = 0;
if (ret == 0) {
kem = OQS_KEM_new(OQS_KEM_alg_saber_lightsaber);
if (kem == NULL) {
ret = WS_INVALID_ALGO_ID;
}
}
if (ret == 0) {
if (fSz != kem->length_ciphertext) {
ret = WS_BUFFER_E;
}
}
if (OQS_KEM_decaps(kem, ssh->k, f, ssh->handshake->x)
!= OQS_SUCCESS) {
ret = WS_ERROR;
}
if (ret == 0) {
ssh->kSz = kem->length_shared_secret;
}
#else
ret = WS_INVALID_ALGO_ID;
#endif
} else {
ret = WS_INVALID_ALGO_ID;
}
}
if (ret == 0)
@ -6617,6 +6666,9 @@ static const char cannedKeyAlgoEcc521Names[] = "ecdsa-sha2-nistp521";
#endif
static const char cannedKexAlgoNames[] =
#if !defined(WOLFSSH_NO_SABER_LEVEL1_SHA256)
"saber-lightsaber-sha256,"
#endif
#if !defined(WOLFSSH_NO_ECDH_SHA2_NISTP521)
"ecdh-sha2-nistp521,"
#endif
@ -6642,7 +6694,8 @@ static const char cannedKexAlgoNames[] =
defined(WOLFSSH_NO_DH_GROUP14_SHA1) && \
defined(WOLFSSH_NO_DH_GROUP1_SHA1) && \
defined(WOLFSSH_NO_ECDH_SHA2_NISTP521) && \
defined(WOLFSSH_NO_ECDH_SHA2_NISTP384)
defined(WOLFSSH_NO_ECDH_SHA2_NISTP384) && \
defined(WOLFSSH_NO_SABER_LEVEL1_256)
#warning "You need at least one key exchange algorithm."
#endif
@ -6827,7 +6880,13 @@ struct wolfSSH_sigKeyBlockFull {
} sk;
};
#define KEX_F_SIZE (256 + 1)
#ifndef WOLFSSH_NO_SABER_LEVEL1_SHA256
/* Size of SABER Level1 ciphertext. */
#define KEX_F_SIZE 736
#else
#define KEX_F_SIZE (256 + 1)
#endif
#define KEX_SIG_SIZE (512)
@ -6851,6 +6910,7 @@ int SendKexDhReply(WOLFSSH* ssh)
word32 fSz = KEX_F_SIZE;
word32 sigSz = KEX_SIG_SIZE;
byte useEcc = 0;
byte useSaber = 0;
byte fPad = 0;
byte kPad = 0;
word32 sigBlockSz = 0;
@ -6952,6 +7012,12 @@ int SendKexDhReply(WOLFSSH* ssh)
useEcc = 1;
msgId = MSGID_KEXDH_REPLY;
break;
#endif
#ifndef WOLFSSH_NO_SABER_LEVEL1_SHA256
case ID_SABER_LEVEL1_SHA256:
useSaber = 1; /* Only support level 1 for now. */
msgId = MSGID_KEXKEM_REPLY;
break;
#endif
default:
ret = WS_INVALID_ALGO_ID;
@ -7246,7 +7312,7 @@ int SendKexDhReply(WOLFSSH* ssh)
/* Make the server's DH f-value and the shared secret K. */
/* Or make the server's ECDH private value, and the shared secret K. */
if (ret == 0) {
if (!useEcc) {
if ((!useEcc) && (!useSaber)) {
#ifndef WOLFSSH_NO_DH
word32 ySz = MAX_KEX_KEY_SZ;
#ifdef WOLFSSH_SMALL_STACK
@ -7288,7 +7354,7 @@ int SendKexDhReply(WOLFSSH* ssh)
#endif
#endif /* ! WOLFSSH_NO_DH */
}
else {
else if (useEcc) {
#if !defined(WOLFSSH_NO_ECDH)
#ifdef WOLFSSH_SMALL_STACK
ecc_key *pubKey = NULL, *privKey = NULL;
@ -7355,11 +7421,47 @@ int SendKexDhReply(WOLFSSH* ssh)
privKey = NULL;
#endif
#endif /* !defined(WOLFSSH_NO_ECDH) */
} else if (useSaber) {
#ifndef WOLFSSH_NO_SABER_LEVEL1_SHA256
/* This is a KEM. In this case, I need to encapsulate the
* shared secret. */
OQS_KEM* kem = NULL;
ret = 0;
if (ret == 0) {
kem = OQS_KEM_new(OQS_KEM_alg_saber_lightsaber);
if (kem == NULL) {
ret = WS_INVALID_ALGO_ID;
}
}
if (ret == 0) {
if (ssh->handshake->eSz != kem->length_public_key) {
ret = WS_PUBKEY_REJECTED_E;
}
}
if (ret == 0) {
if (OQS_KEM_encaps(kem, f_ptr, ssh->k, ssh->handshake->e)
!= OQS_SUCCESS) {
ret = WS_PUBKEY_REJECTED_E;
}
}
if (ret == 0) {
ssh->kSz = kem->length_shared_secret;
fSz = kem->length_ciphertext;
}
#endif
} else {
/* This should never happen */
ret = WS_ERROR;
}
}
/* Hash in the server's DH f-value. */
if (ret == 0) {
/* Do not want leading zero's removed for SABER. */
if ((ret == 0) && (!useSaber)) {
ret = CreateMpint(f_ptr, &fSz, &fPad);
}
if (ret == 0) {
@ -7923,6 +8025,13 @@ int SendKexDhInit(WOLFSSH* ssh)
ssh->handshake->useEcc = 1;
msgId = MSGID_KEXECDH_INIT;
break;
#endif
#ifndef WOLFSSH_NO_SABER_LEVEL1_SHA256
case ID_SABER_LEVEL1_SHA256:
/* Only support level 1 for now. */
ssh->handshake->useSaber = 1;
msgId = MSGID_KEXKEM_INIT;
break;
#endif
default:
WLOG(WS_LOG_DEBUG, "Invalid algo: %u", ssh->handshake->kexId);
@ -7931,7 +8040,7 @@ int SendKexDhInit(WOLFSSH* ssh)
if (ret == WS_SUCCESS) {
if (!ssh->handshake->useEcc) {
if ((!ssh->handshake->useEcc) && (!ssh->handshake->useSaber)) {
#ifndef WOLFSSH_NO_DH
DhKey* privKey = &ssh->handshake->privKey.dh;
@ -7946,7 +8055,7 @@ int SendKexDhInit(WOLFSSH* ssh)
e, &eSz);
#endif
}
else {
else if (ssh->handshake->useEcc) {
#if !defined(WOLFSSH_NO_ECDH)
ecc_key* privKey = &ssh->handshake->privKey.ecc;
int primeId = wcPrimeForId(ssh->handshake->kexId);
@ -7977,15 +8086,43 @@ int SendKexDhInit(WOLFSSH* ssh)
#else
ret = WS_INVALID_ALGO_ID;
#endif /* !defined(WOLFSSH_NO_ECDH) */
} else if (ssh->handshake->useSaber) {
#ifndef WOLFSSH_NO_SABER_LEVEL1_SHA256
OQS_KEM* kem = NULL;
ret = 0;
if (ret == 0) {
kem = OQS_KEM_new(OQS_KEM_alg_saber_lightsaber);
if (kem == NULL) {
ret = WS_INVALID_ALGO_ID;
}
}
if (ret == 0) {
if (OQS_KEM_keypair(kem, e, ssh->handshake->x)
!= OQS_SUCCESS) {
/* This should never happen */
ret = WS_ERROR;
}
eSz = kem->length_public_key;
ssh->handshake->xSz = kem->length_secret_key;
}
#else
ret = WS_INVALID_ALGO_ID;
#endif
} else {
ret = WS_INVALID_ALGO_ID;
}
if (ret == 0)
ret = WS_SUCCESS;
}
if (ret == WS_SUCCESS) {
/* Do not want leading zero's removed for SABER. */
if ((ret == WS_SUCCESS) && (!ssh->handshake->useSaber)) {
ret = CreateMpint(e, &eSz, &ePad);
}
if (ret == WS_SUCCESS) {
if (ePad == 1) {
ssh->handshake->e[0] = 0;

View File

@ -132,6 +132,10 @@ extern "C" {
#undef WOLFSSH_NO_ECDH_SHA2_ED25519
#define WOLFSSH_NO_ECDH_SHA2_ED25519
#endif
#if !defined(WOLFSSH_HAVE_LIBOQS) || defined(NO_SHA256)
#undef WOLFSSH_NO_SABER_LEVEL1_SHA256
#define WOLFSSH_NO_SABER_LEVEL1_SHA256
#endif
#if defined(WOLFSSH_NO_DH_GROUP1_SHA1) && \
defined(WOLFSSH_NO_DH_GROUP14_SHA1) && \
@ -139,7 +143,8 @@ extern "C" {
defined(WOLFSSH_NO_ECDH_SHA2_NISTP256) && \
defined(WOLFSSH_NO_ECDH_SHA2_NISTP384) && \
defined(WOLFSSH_NO_ECDH_SHA2_NISTP521) && \
defined(WOLFSSH_NO_ECDH_SHA2_ED25519)
defined(WOLFSSH_NO_ECDH_SHA2_ED25519) && \
defined(WOLFSSH_NO_SABER_LEVEL1_SHA256)
#error "You need at least one key agreement algorithm."
#endif
@ -266,6 +271,7 @@ enum {
ID_ECDH_SHA2_ED25519,
ID_ECDH_SHA2_ED25519_LIBSSH,
ID_DH_GROUP14_SHA256,
ID_SABER_LEVEL1_SHA256,
/* Public Key IDs */
ID_SSH_RSA,
@ -342,8 +348,13 @@ enum {
#define WOLFSSH_DEFAULT_GEXDH_MAX 8192
#endif
#ifndef MAX_KEX_KEY_SZ
/* This is based on the 8192-bit DH key that is the max size. */
#define MAX_KEX_KEY_SZ (WOLFSSH_DEFAULT_GEXDH_MAX / 8)
#ifndef WOLFSSH_NO_SABER_LEVEL1_SHA256
/* Private key size of SABER Level1. Biggest artifact. */
#define MAX_KEX_KEY_SZ 1568
#else
/* This is based on the 8192-bit DH key that is the max size. */
#define MAX_KEX_KEY_SZ (WOLFSSH_DEFAULT_GEXDH_MAX / 8)
#endif
#endif
#ifndef WOLFSSH_MAX_FILE_SIZE
#define WOLFSSH_MAX_FILE_SIZE (1024ul * 1024ul * 4)
@ -404,6 +415,7 @@ struct WOLFSSH_CTX {
byte* privateKey; /* Owned by CTX */
word32 privateKeySz;
byte useEcc; /* Depends on the private key */
byte useSaber; /* Depends on the private key */
word32 highwaterMark;
const char* banner;
word32 bannerSz;
@ -467,6 +479,7 @@ typedef struct HandshakeInfo {
#endif
byte useEcc;
byte useSaber;
union {
#ifndef WOLFSSH_NO_DH
DhKey dh;
@ -889,9 +902,12 @@ enum WS_MessageIds {
MSGID_KEXDH_INIT = 30,
MSGID_KEXECDH_INIT = 30,
MSGID_KEXKEM_INIT = 30,
MSGID_KEXDH_REPLY = 31,
MSGID_KEXECDH_REPLY = 31,
MSGID_KEXKEM_REPLY = 31,
MSGID_KEXDH_GEX_GROUP = 31,
MSGID_KEXDH_GEX_INIT = 32,
MSGID_KEXDH_GEX_REPLY = 33,