From 365d1fd8eac61ae13de666f6ba8c6a7b11957418 Mon Sep 17 00:00:00 2001 From: John Safranek Date: Fri, 17 Jul 2020 14:51:45 -0700 Subject: [PATCH] SSH-AGENT 1. Added option to client to load a public key. 2. Added function ReadKey to load a key from a buffer or from a file and store it. Utility for the client. --- examples/client/client.c | 147 ++++++++++++++++++++---------- src/ssh.c | 192 +++++++++++++++++++++++++++++++++++++++ wolfssh/internal.h | 4 + wolfssh/port.h | 1 + wolfssh/ssh.h | 8 +- 5 files changed, 302 insertions(+), 50 deletions(-) diff --git a/examples/client/client.c b/examples/client/client.c index 2353130..4f1147b 100644 --- a/examples/client/client.c +++ b/examples/client/client.c @@ -26,7 +26,6 @@ #include #endif #include -#include #include "examples/client/client.h" #if !defined(USE_WINDOWS_API) && !defined(MICROCHIP_PIC32) #include @@ -158,6 +157,8 @@ static void ShowUsage(void) printf(" -u username to authenticate as (REQUIRED)\n"); printf(" -P password for username, prompted if omitted\n"); printf(" -e use sample ecc key for user\n"); + printf(" -i filename for the user's private key\n"); + printf(" -j filename for the user's public key\n"); printf(" -x exit after successful connection without doing\n" " read/write\n"); printf(" -N use non-blocking sockets\n"); @@ -174,20 +175,24 @@ static void ShowUsage(void) static byte userPassword[256]; -static byte userPublicKeyType[32]; static byte userPublicKey[512]; -static word32 userPublicKeySz; -static const byte* userPrivateKey; -static word32 userPrivateKeySz; +static const byte* userPublicKeyType = NULL; +static byte* userPrivateKey = NULL; /* Will be allocated by Read Key. */ +static const byte* userPrivateKeyType = NULL; +static word32 userPublicKeySz = 0; +static word32 userPublicKeyTypeSz = 0; +static word32 userPrivateKeySz = 0; +static word32 userPrivateKeyTypeSz = 0; +static byte isPrivate = 0; -static const char hanselPublicRsa[] = - "AAAAB3NzaC1yc2EAAAADAQABAAABAQC9P3ZFowOsONXHD5MwWiCciXytBRZGho" +static const char* hanselPublicRsa = + "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC9P3ZFowOsONXHD5MwWiCciXytBRZGho" "MNiisWSgUs5HdHcACuHYPi2W6Z1PBFmBWT9odOrGRjoZXJfDDoPi+j8SSfDGsc/hsCmc3G" "p2yEhUZUEkDhtOXyqjns1ickC9Gh4u80aSVtwHRnJZh9xPhSq5tLOhId4eP61s+a5pwjTj" "nEhBaIPUJO2C/M0pFnnbZxKgJlX7t1Doy7h5eXxviymOIvaCZKU+x5OopfzM/wFkey0EPW" "NmzI5y/+pzU5afsdeEWdiQDIQc80H6Pz8fsoFPvYSG+s4/wz0duu7yeeV1Ypoho65Zr+pE" - "nIf7dO0B8EblgWt+ud+JI8wrAhfE4x"; + "nIf7dO0B8EblgWt+ud+JI8wrAhfE4x hansel"; static const byte hanselPrivateRsa[] = { 0x30, 0x82, 0x04, 0xa3, 0x02, 0x01, 0x00, 0x02, 0x82, 0x01, 0x01, 0x00, @@ -295,9 +300,10 @@ static const byte hanselPrivateRsa[] = { static const unsigned int hanselPrivateRsaSz = 1191; -static const char hanselPublicEcc[] = - "AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBNkI5JTP6D0lF42tbx" - "X19cE87hztUS6FSDoGvPfiU0CgeNSbI+aFdKIzTP5CQEJSvm25qUzgDtH7oyaQROUnNvk="; +const char* hanselPublicEcc = + "ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAA" + "BBBNkI5JTP6D0lF42tbxX19cE87hztUS6FSDoGvPfiU0CgeNSbI+aFdKIzTP5CQEJSvm25" + "qUzgDtH7oyaQROUnNvk= hansel"; static const byte hanselPrivateEcc[] = { 0x30, 0x77, 0x02, 0x01, 0x01, 0x04, 0x20, 0x03, 0x6e, 0x17, 0xd3, 0xb9, @@ -336,13 +342,16 @@ static int wsUserAuth(byte authType, #endif /* We know hansel has a key, wait for request of public key */ - if (authType == WOLFSSH_USERAUTH_PUBLICKEY && + if ((authData->type & WOLFSSH_USERAUTH_PUBLICKEY) && authData->username != NULL && authData->usernameSz > 0 && - XSTRNCMP((char*)authData->username, "hansel", - authData->usernameSz) == 0) { + (XSTRNCMP((char*)authData->username, "hansel", + authData->usernameSz) == 0 || + XSTRNCMP((char*)authData->username, "john", + authData->usernameSz) == 0)) { if (authType == WOLFSSH_USERAUTH_PASSWORD) { - printf("rejecting password type with hansel in favor of pub key\n"); + printf("rejecting password type with %s in favor of pub key\n", + (char*)authData->username); return WOLFSSH_USERAUTH_FAILURE; } } @@ -350,21 +359,14 @@ static int wsUserAuth(byte authType, if (authType == WOLFSSH_USERAUTH_PUBLICKEY) { WS_UserAuthData_PublicKey* pk = &authData->sf.publicKey; - /* we only have hansel's sample key loaded */ - if (authData->username != NULL && authData->usernameSz > 0 && - XSTRNCMP((char*)authData->username, "hansel", - authData->usernameSz) == 0) { - pk->publicKeyType = userPublicKeyType; - pk->publicKeyTypeSz = (word32)WSTRLEN((char*)userPublicKeyType); - pk->publicKey = userPublicKey; - pk->publicKeySz = userPublicKeySz; - pk->privateKey = userPrivateKey; - pk->privateKeySz = userPrivateKeySz; - ret = WOLFSSH_USERAUTH_SUCCESS; - } - else { - ret = WOLFSSH_USERAUTH_INVALID_USER; - } + pk->publicKeyType = userPublicKeyType; + pk->publicKeyTypeSz = userPublicKeyTypeSz; + pk->publicKey = userPublicKey; + pk->publicKeySz = userPublicKeySz; + pk->privateKey = userPrivateKey; + pk->privateKeySz = userPrivateKeySz; + + ret = WOLFSSH_USERAUTH_SUCCESS; } else if (authType == WOLFSSH_USERAUTH_PASSWORD) { const char* defaultPassword = (const char*)ctx; @@ -383,7 +385,7 @@ static int wsUserAuth(byte authType, ret = WOLFSSH_USERAUTH_FAILURE; } else { - char* c = strpbrk((char*)userPassword, "\r\n");; + char* c = strpbrk((char*)userPassword, "\r\n"); if (c != NULL) *c = '\0'; } @@ -752,6 +754,8 @@ THREAD_RETURN WOLFSSH_THREAD client_test(void* args) const char* username = NULL; const char* password = NULL; const char* cmd = NULL; + const char* privKeyName = NULL; + const char* pubKeyName = NULL; byte imExit = 0; byte nonBlock = 0; byte keepOpen = 0; @@ -766,7 +770,7 @@ THREAD_RETURN WOLFSSH_THREAD client_test(void* args) char** argv = ((func_args*)args)->argv; ((func_args*)args)->return_code = 0; - while ((ch = mygetopt(argc, argv, "?c:eh:p:tu:xzNP:R")) != -1) { + while ((ch = mygetopt(argc, argv, "?c:eh:i:j:p:tu:xzNP:R")) != -1) { switch (ch) { case 'h': host = myoptarg; @@ -799,6 +803,14 @@ THREAD_RETURN WOLFSSH_THREAD client_test(void* args) password = myoptarg; break; + case 'i': + privKeyName = myoptarg; + break; + + case 'j': + pubKeyName = myoptarg; + break; + case 'x': /* exit after successful connection without read/write */ imExit = 1; @@ -843,27 +855,62 @@ THREAD_RETURN WOLFSSH_THREAD client_test(void* args) err_sys("Threading needed for terminal session\n"); #endif - if (userEcc) { - userPublicKeySz = (word32)sizeof(userPublicKey); - Base64_Decode((byte*)hanselPublicEcc, - (word32)WSTRLEN(hanselPublicEcc), - (byte*)userPublicKey, &userPublicKeySz); + if (pubKeyName == NULL && privKeyName != NULL) { + err_sys("If setting priv key, need pub key."); + } - WSTRNCPY((char*)userPublicKeyType, "ecdsa-sha2-nistp256", - sizeof(userPublicKeyType)); - userPrivateKey = hanselPrivateEcc; - userPrivateKeySz = hanselPrivateEccSz; + if (privKeyName == NULL) { + if (userEcc) { + ret = wolfSSH_ReadKey_buffer(hanselPrivateEcc, hanselPrivateEccSz, + WOLFSSH_FORMAT_ASN1, &userPrivateKey, &userPrivateKeySz, + &userPrivateKeyType, &userPrivateKeyTypeSz, NULL); + isPrivate = 1; + } + else { + ret = wolfSSH_ReadKey_buffer(hanselPrivateRsa, hanselPrivateRsaSz, + WOLFSSH_FORMAT_ASN1, &userPrivateKey, &userPrivateKeySz, + &userPrivateKeyType, &userPrivateKeyTypeSz, NULL); + isPrivate = 1; + } + if (ret != 0) err_sys("Couldn't load private key buffer."); } else { - userPublicKeySz = (word32)sizeof(userPublicKey); - Base64_Decode((byte*)hanselPublicRsa, - (word32)WSTRLEN(hanselPublicRsa), - (byte*)userPublicKey, &userPublicKeySz); + ret = wolfSSH_ReadKey_file(privKeyName, + (byte**)&userPrivateKey, &userPrivateKeySz, + (const byte**)&userPrivateKeyType, &userPrivateKeyTypeSz, + &isPrivate, NULL); + if (ret != 0) err_sys("Couldn't load private key file."); + } - WSTRNCPY((char*)userPublicKeyType, "ssh-rsa", - sizeof(userPublicKeyType)); - userPrivateKey = hanselPrivateRsa; - userPrivateKeySz = hanselPrivateRsaSz; + if (pubKeyName == NULL) { + byte* p = userPublicKey; + userPublicKeySz = sizeof(userPublicKey); + + if (userEcc) { + ret = wolfSSH_ReadKey_buffer((const byte*)hanselPublicEcc, + (word32)strlen(hanselPublicEcc), WOLFSSH_FORMAT_SSH, + &p, &userPublicKeySz, + &userPublicKeyType, &userPublicKeyTypeSz, NULL); + isPrivate = 1; + } + else { + ret = wolfSSH_ReadKey_buffer((const byte*)hanselPublicRsa, + (word32)strlen(hanselPublicRsa), WOLFSSH_FORMAT_SSH, + &p, &userPublicKeySz, + &userPublicKeyType, &userPublicKeyTypeSz, NULL); + isPrivate = 1; + } + if (ret != 0) err_sys("Couldn't load public key buffer."); + } + else { + byte* p = userPublicKey; + userPublicKeySz = sizeof(userPublicKey); + + ret = wolfSSH_ReadKey_file(pubKeyName, + &p, &userPublicKeySz, + (const byte**)&userPublicKeyType, &userPublicKeyTypeSz, + &isPrivate, NULL); + if (ret != 0) err_sys("Couldn't load public key file."); } ctx = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_CLIENT, NULL); @@ -1007,6 +1054,8 @@ THREAD_RETURN WOLFSSH_THREAD client_test(void* args) WCLOSESOCKET(sockFd); wolfSSH_free(ssh); wolfSSH_CTX_free(ctx); + if (userPrivateKey != NULL) + free(userPrivateKey); if (ret != WS_SUCCESS) err_sys("Closing stream failed. Connection could have been closed by peer"); diff --git a/src/ssh.c b/src/ssh.c index 7a3540b..c34a91e 100644 --- a/src/ssh.c +++ b/src/ssh.c @@ -1362,6 +1362,198 @@ char* wolfSSH_GetUsername(WOLFSSH* ssh) } +#include +#include +#include + +#define WSTRDUP(x,y) strdup((x)) +#define WSTRSEP(x,y) strsep((x),(y)) +int wolfSSH_ReadKey_buffer(const byte* in, word32 inSz, int format, + byte** out, word32* outSz, const byte** outType, word32* outTypeSz, + void* heap) +{ + int ret = WS_SUCCESS; + + (void)heap; + + if (in == NULL || inSz == 0 || out == NULL || outSz == NULL || + outType == NULL || outTypeSz == NULL) + return WS_BAD_ARGUMENT; + + if (format == WOLFSSH_FORMAT_SSH) { + char* dup; + char* c; + char* type; + char* key; + + /* + SSH format is: + type AAAABASE64ENCODEDKEYDATA comment + */ + c = dup = WSTRDUP((const char*)in, heap); + type = WSTRSEP(&c, " \n"); + key = WSTRSEP(&c, " \n"); + + if (type != NULL && key != NULL) { + const char* name; + word32 typeSz; + + typeSz = (word32)WSTRLEN(type); + + name = IdToName(ID_SSH_RSA); + if (WSTRNCMP(type, name, typeSz) == 0) { + *outType = (const byte*)name; + } + else { + name = IdToName(ID_ECDSA_SHA2_NISTP256); + if (WSTRNCMP(type, name, typeSz) == 0) { + *outType = (const byte*)name; + } + else { + name = IdToName(ID_UNKNOWN); + *outType = (const byte*)name; + typeSz = (word32)WSTRLEN(name); + } + } + *outTypeSz = typeSz; + + ret = Base64_Decode((byte*)key, (word32)WSTRLEN(key), *out, outSz); + } + + if (ret != 0) + ret = WS_ERROR; + + WFREE(dup, heap, DYNTYPE_STRING); + } + else if (format == WOLFSSH_FORMAT_ASN1) { + byte* newKey; + union { + RsaKey rsa; + ecc_key ecc; + } testKey; + word32 scratch = 0; + + if (*out == NULL) { + newKey = (byte*)WMALLOC(inSz, heap, DYNTYPE_PRIVKEY); + if (newKey == NULL) + return WS_MEMORY_E; + *out = newKey; + } + else { + if (*outSz < inSz) + return WS_ERROR; + newKey = *out; + } + *outSz = inSz; + WMEMCPY(newKey, in, inSz); + + /* TODO: This is copied and modified from a function in src/internal.c. + This and that code should be combined into a single function. */ + if (wc_InitRsaKey(&testKey.rsa, heap) < 0) + return WS_RSA_E; + + ret = wc_RsaPrivateKeyDecode(in, &scratch, &testKey.rsa, inSz); + + wc_FreeRsaKey(&testKey.rsa); + + if (ret == 0) { + *outType = (const byte*)IdToName(ID_SSH_RSA); + *outTypeSz = (word32)WSTRLEN((const char*)*outType); + } + else { + /* Couldn't decode as RSA testKey. Try decoding as ECC testKey. */ + scratch = 0; + if (wc_ecc_init_ex(&testKey.ecc, heap, INVALID_DEVID) != 0) + return WS_ECC_E; + + ret = wc_EccPrivateKeyDecode(in, &scratch, + &testKey.ecc, inSz); + wc_ecc_free(&testKey.ecc); + + if (ret == 0) { + *outType = (const byte*)IdToName(ID_ECDH_SHA2_NISTP256); + *outTypeSz = (word32)WSTRLEN((const char*)*outType); + } + else + return WS_BAD_FILE_E; + } + } + else + ret = WS_ERROR; + + return ret; +} + + +#ifndef NO_FILESYSTEM + +int wolfSSH_ReadKey_file(const char* name, + byte** out, word32* outSz, const byte** outType, word32* outTypeSz, + byte* isPrivate, void* heap) +{ + WFILE* file; + byte* in; + word32 inSz; + int format; + int ret; + + if (name == NULL) + return WS_BAD_FILE_E; + + if (out == NULL || outSz == NULL || outType == NULL || outTypeSz == NULL || + isPrivate == NULL) + return WS_BAD_ARGUMENT; + + ret = WFOPEN(&file, name, "rb"); + if (file == WBADFILE) return WS_BAD_FILE_E; + if (WFSEEK(file, 0, WSEEK_END) != 0) { + WFCLOSE(file); + return WS_BAD_FILE_E; + } + inSz = (word32)WFTELL(file); + WREWIND(file); + + if (inSz > WOLFSSH_MAX_FILE_SIZE || inSz == 0) { + WFCLOSE(file); + return WS_BAD_FILE_E; + } + + in = (byte*)WMALLOC(inSz + 1, heap, DYNTYPE_FILE); + if (in == NULL) { + WFCLOSE(file); + return WS_MEMORY_E; + } + + ret = (int)XFREAD(in, 1, inSz, file); + if (ret <= 0 || (word32)ret != inSz) { + ret = WS_BAD_FILE_E; + } + else { + if (WSTRNSTR((const char*)in, + "ssh-rsa", inSz) == (const char*)in || + WSTRNSTR((const char*)in, + "ecdsa-sha2-nistp", inSz) == (const char*)in) { + *isPrivate = 0; + format = WOLFSSH_FORMAT_SSH; + in[inSz] = 0; + } + else { + *isPrivate = 1; + format = WOLFSSH_FORMAT_ASN1; + } + + ret = wolfSSH_ReadKey_buffer(in, inSz, format, + out, outSz, outType, outTypeSz, heap); + } + + WFCLOSE(file); + WFREE(in, heap, DYNTYPE_FILE); + + return ret; +} + +#endif + int wolfSSH_CTX_SetBanner(WOLFSSH_CTX* ctx, const char* newBanner) { diff --git a/wolfssh/internal.h b/wolfssh/internal.h index ab4bbbb..795ce34 100644 --- a/wolfssh/internal.h +++ b/wolfssh/internal.h @@ -163,6 +163,9 @@ enum { /* 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 +#ifndef WOLFSSH_MAX_FILE_SIZE + #define WOLFSSH_MAX_FILE_SIZE (1024ul * 1024ul * 4) +#endif WOLFSSH_LOCAL byte NameToId(const char*, word32); WOLFSSH_LOCAL const char* IdToName(byte); @@ -744,6 +747,7 @@ enum WS_DynamicTypes { DYNTYPE_AGENT_ID, DYNTYPE_AGENT_KEY, DYNTYPE_AGENT_BUFFER, + DYNTYPE_FILE, DYNTYPE_TEMP }; diff --git a/wolfssh/port.h b/wolfssh/port.h index 17f59f7..06a789c 100644 --- a/wolfssh/port.h +++ b/wolfssh/port.h @@ -222,6 +222,7 @@ extern "C" { #define WFTELL(s) ftell((s)) #define WREWIND(s) rewind((s)) #define WSEEK_END SEEK_END + #define WBADFILE NULL #ifdef WOLFSSL_VXWORKS #define WUTIMES(f,t) (WS_SUCCESS) #else diff --git a/wolfssh/ssh.h b/wolfssh/ssh.h index ab39c42..5cf43e2 100644 --- a/wolfssh/ssh.h +++ b/wolfssh/ssh.h @@ -80,6 +80,11 @@ WOLFSSH_API void wolfSSH_SetHighwaterCb(WOLFSSH_CTX*, word32, WOLFSSH_API void wolfSSH_SetHighwaterCtx(WOLFSSH*, void*); WOLFSSH_API void* wolfSSH_GetHighwaterCtx(WOLFSSH*); +WOLFSSH_API int wolfSSH_ReadKey_buffer(const byte*, word32, int, + byte**, word32*, const byte**, word32*, void*); +WOLFSSH_API int wolfSSH_ReadKey_file(const char*, + byte**, word32*, const byte**, word32*, byte*, void*); + #define WS_CHANNEL_ID_SELF 0 #define WS_CHANNEL_ID_PEER 1 @@ -237,7 +242,8 @@ enum WS_EndpointTypes { enum WS_FormatTypes { WOLFSSH_FORMAT_ASN1, WOLFSSH_FORMAT_PEM, - WOLFSSH_FORMAT_RAW + WOLFSSH_FORMAT_RAW, + WOLFSSH_FORMAT_SSH, };