wolfssh/apps/wolfssh/common.c

758 lines
20 KiB
C

/* common.c
*
* Copyright (C) 2014-2024 wolfSSL Inc.
*
* This file is part of wolfSSH.
*
* wolfSSH is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* wolfSSH is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with wolfSSH. If not, see <http://www.gnu.org/licenses/>.
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#define WOLFSSH_TEST_CLIENT
#include <wolfssh/ssh.h>
#include <wolfssh/internal.h>
#include <wolfssh/wolfsftp.h>
#include <wolfssh/port.h>
#include <wolfssl/wolfcrypt/ecc.h>
#include <wolfssl/wolfcrypt/coding.h>
#include "apps/wolfssh/common.h"
#ifndef USE_WINDOWS_API
#include <termios.h>
#endif
#ifdef WOLFSSH_CERTS
#include <wolfssl/wolfcrypt/asn.h>
#endif
static byte userPublicKeyBuf[512];
static byte* userPublicKey = userPublicKeyBuf;
static const byte* userPublicKeyType = NULL;
static byte userPassword[256];
static const byte* userPrivateKeyType = NULL;
static word32 userPublicKeySz = 0;
static byte pubKeyLoaded = 0; /* was a public key loaded */
static byte userPrivateKeyBuf[1191];
static byte* userPrivateKey = userPrivateKeyBuf;
static word32 userPublicKeyTypeSz = 0;
static word32 userPrivateKeySz = sizeof(userPrivateKeyBuf);
static word32 userPrivateKeyTypeSz = 0;
static byte isPrivate = 0;
#ifdef WOLFSSH_CERTS
#if 0
/* compiled in for using RSA certificates instead of ECC certificate */
static const byte publicKeyType[] = "x509v3-ssh-rsa";
static const byte privateKeyType[] = "ssh-rsa";
#else
static const byte publicKeyType[] = "x509v3-ecdsa-sha2-nistp256";
#endif
#endif
static inline void ato32(const byte* c, word32* u32)
{
*u32 = (c[0] << 24) | (c[1] << 16) | (c[2] << 8) | c[3];
}
static int load_der_file(const char* filename, byte** out, word32* outSz)
{
WFILE* file;
byte* in;
word32 inSz;
int ret;
if (filename == NULL || out == NULL || outSz == NULL)
return -1;
ret = WFOPEN(NULL, &file, filename, "rb");
if (ret != 0 || file == WBADFILE)
return -1;
if (WFSEEK(NULL, file, 0, WSEEK_END) != 0) {
WFCLOSE(NULL, file);
return -1;
}
inSz = (word32)WFTELL(NULL, file);
WREWIND(NULL, file);
if (inSz == 0) {
WFCLOSE(NULL, file);
return -1;
}
in = (byte*)WMALLOC(inSz, NULL, 0);
if (in == NULL) {
WFCLOSE(NULL, file);
return -1;
}
ret = (int)WFREAD(NULL, in, 1, inSz, file);
if (ret <= 0 || (word32)ret != inSz) {
ret = -1;
WFREE(in, NULL, 0);
in = 0;
inSz = 0;
}
else
ret = 0;
*out = in;
*outSz = inSz;
WFCLOSE(NULL, file);
return ret;
}
#if defined(WOLFSSH_CERTS)
#if (defined(OPENSSL_ALL) || defined(WOLFSSL_IP_ALT_NAME))
/* when set as true then ignore miss matching IP addresses */
static int IPOverride = 0;
static int ParseRFC6187(const byte* in, word32 inSz, byte** leafOut,
word32* leafOutSz)
{
int ret = WS_SUCCESS;
word32 l = 0, m = 0;
if (inSz < sizeof(word32)) {
printf("inSz %d too small for holding cert name\n", inSz);
return WS_BUFFER_E;
}
/* Skip the name */
ato32(in, &l);
m += l + sizeof(word32);
/* Get the cert count */
if (ret == WS_SUCCESS) {
word32 count;
if (inSz - m < sizeof(word32))
return WS_BUFFER_E;
ato32(in + m, &count);
m += sizeof(word32);
if (ret == WS_SUCCESS && count == 0)
ret = WS_FATAL_ERROR; /* need at least one cert */
}
if (ret == WS_SUCCESS) {
word32 certSz = 0;
if (inSz - m < sizeof(word32))
return WS_BUFFER_E;
ato32(in + m, &certSz);
m += sizeof(word32);
if (ret == WS_SUCCESS) {
/* store leaf cert size to present to user callback */
*leafOutSz = certSz;
*leafOut = (byte*)in + m;
}
if (inSz - m < certSz)
return WS_BUFFER_E;
}
return ret;
}
void ClientIPOverride(int flag)
{
IPOverride = flag;
}
#endif /* OPENSSL_ALL || WOLFSSL_IP_ALT_NAME */
#endif /* WOLFSSH_CERTS */
static int AppendKeyToFile(const char* filename, const char* name,
const char* type, const char* key)
{
WFILE *f;
int ret;
ret = WFOPEN(NULL, &f, filename, "a");
if (ret == 0 && f != WBADFILE) {
fprintf(f, "%s %s %s\n", name, type, key);
WFCLOSE(NULL, f);
}
return ret;
}
static int FingerprintKey(const byte* pubKey, word32 pubKeySz, char* out)
{
wc_Sha256 sha;
byte digest[WC_SHA256_DIGEST_SIZE];
char fp[48] = { 0 };
word32 fpSz = sizeof(fp);
int ret;
ret = wc_InitSha256(&sha);
if (ret == 0)
ret = wc_Sha256Update(&sha, pubKey, pubKeySz);
if (ret == 0)
ret = wc_Sha256Final(&sha, digest);
if (ret == 0)
ret = Base64_Encode_NoNl(digest, sizeof(digest), (byte*)fp, &fpSz);
if (ret == 0) {
if (fp[fpSz] == '=') {
fp[fpSz] = 0;
}
WSTRCAT(out, "SHA256:");
WSTRCAT(out, fp);
}
return ret;
}
static int GetConfirmation(void)
{
int c, confirmed = 0;
printf("Y/n: ");
c = getchar();
if (c == 'Y') {
confirmed = 1;
}
return confirmed;
}
#define WOLFSSH_CLIENT_ENCKEY_SIZE_ESTIMATE 1200
#define WOLFSSH_CLIENT_PUBKEYTYPE_SIZE_ESTIMATE 54
#define WOLFSSH_CLIENT_FINGERPRINT_SIZE_ESTIMATE 56
int ClientPublicKeyCheck(const byte* pubKey, word32 pubKeySz, void* ctx)
{
char *cursor;
char *line;
const char *targetName = (const char*)ctx;
char *name;
char *keyType;
char *key;
char *knownHosts = NULL;
char *knownHostsName = NULL;
char *encodedKey = NULL;
char *pubKeyType = NULL;
char *fp = NULL;
int ret = 0, found = 0, badMatch = 0, otherMatch = 0;
word32 sz, lineCount = 0;
{
const char *defaultName = "/.ssh/known_hosts";
char *env;
env = getenv("HOME");
if (env != NULL) {
sz = (word32)(WSTRLEN(env) + WSTRLEN(defaultName) + 1);
knownHostsName = (char*)WMALLOC(sz, NULL, 0);
if (knownHostsName != NULL) {
WSTRCPY(knownHostsName, env);
WSTRCAT(knownHostsName, defaultName);
}
}
else
ret = -1;
}
if (ret == 0) {
sz = 0;
ret = load_der_file(knownHostsName, (byte**)&knownHosts, &sz);
}
if (ret == 0) {
if (sz < sizeof(word32)) {
/* This file is too small. There must be at least a word32
* length size. */
ret = -1;
}
}
if (ret == 0) {
/* load_der_file() loads exactly what's in the file. Since it is
* NL terminated lines of known host data, and the last line ends
* in a NL, overwrite that with a nul to terminate the new string. */
knownHosts[sz - 1] = 0;
encodedKey = (char*)WMALLOC(WOLFSSH_CLIENT_ENCKEY_SIZE_ESTIMATE
+ WOLFSSH_CLIENT_PUBKEYTYPE_SIZE_ESTIMATE
+ WOLFSSH_CLIENT_FINGERPRINT_SIZE_ESTIMATE, NULL, 0);
if (encodedKey == NULL) {
ret = -1;
}
}
if (ret == 0) {
pubKeyType = encodedKey + WOLFSSH_CLIENT_ENCKEY_SIZE_ESTIMATE;
fp = pubKeyType + WOLFSSH_CLIENT_PUBKEYTYPE_SIZE_ESTIMATE;
encodedKey[0] = 0;
pubKeyType[0] = 0;
fp[0] = 0;
/* Get the key type out of the key. */
ato32(pubKey, &sz);
if ((sz > pubKeySz - sizeof(word32))
|| (sz > WOLFSSH_CLIENT_PUBKEYTYPE_SIZE_ESTIMATE - 1)) {
ret = -1;
}
}
if (ret == 0) {
WMEMCPY(pubKeyType, pubKey + LENGTH_SZ, sz);
pubKeyType[sz] = 0;
sz = WOLFSSH_CLIENT_ENCKEY_SIZE_ESTIMATE;
ret = Base64_Encode_NoNl(pubKey, pubKeySz, (byte*)encodedKey, &sz);
}
if (ret == 0)
ret = FingerprintKey(pubKey, pubKeySz, fp);
cursor = (ret == 0) ? knownHosts : NULL;
while (cursor) {
lineCount++;
line = WSTRSEP(&cursor, "\n");
if (line != NULL && *line) {
name = WSTRSEP(&line, " ");
keyType = WSTRSEP(&line, " ");
key = WSTRSEP(&line, " ");
if (name && keyType && key) {
int nameMatch, keyTypeMatch, keyMatch;
nameMatch = WSTRCMP(targetName , name) == 0;
keyTypeMatch = WSTRCMP(pubKeyType, keyType) == 0;
keyMatch = WSTRCMP(encodedKey, key) == 0;
if (nameMatch) {
if (keyTypeMatch) {
if (keyMatch) {
found = 1;
}
else {
badMatch = 1;
}
break;
}
}
else {
if (keyTypeMatch && keyMatch) {
/* report key used on different address */
if (!otherMatch) {
printf("Key fingerprint is %s.\n", fp);
printf("This key matches other servers:\n");
otherMatch = 1;
}
if (otherMatch) {
printf("\t%s:%u: %s\n",
knownHostsName, lineCount, name);
}
}
}
}
}
}
if (ret == 0) {
if (badMatch) {
printf("That server is known, but that key is not.\n");
printf("Rejecting connection and closing.\n");
ret = -1;
}
else if (otherMatch) {
if (!found) {
printf("Does this key look familiar to you?\n");
printf("Fingerprint: %s\n", fp);
printf("Shall I add it to the known hosts?\n");
/* Query. */
if (GetConfirmation()) {
ret = AppendKeyToFile(knownHostsName,
targetName, pubKeyType, encodedKey);
}
else {
ret = -1;
}
}
else {
printf("This key matched multiple servers including %s.\n",
targetName);
printf("Attempting to connect.\n");
}
}
else if (!found) {
printf("The server is unknown and the key is unknown.\n");
printf("Fingerprint: %s\n", fp);
printf("Shall I add it to the known hosts?\n");
/* Query. */
if (GetConfirmation()) {
ret = AppendKeyToFile(knownHostsName,
targetName, pubKeyType, encodedKey);
}
}
}
#ifdef WOLFSSH_CERTS
#if defined(OPENSSL_ALL) || defined(WOLFSSL_IP_ALT_NAME)
/* try to parse the certificate and check it's IP address */
if (pubKeySz > 0) {
DecodedCert dCert;
byte* der = NULL;
word32 derSz = 0;
if (ParseRFC6187(pubKey, pubKeySz, &der, &derSz) == WS_SUCCESS) {
wc_InitDecodedCert(&dCert, der, derSz, NULL);
if (wc_ParseCert(&dCert, CERT_TYPE, NO_VERIFY, NULL) != 0) {
WLOG(WS_LOG_DEBUG, "public key not a cert");
}
else {
int ipMatch = 0;
DNS_entry* current = dCert.altNames;
if (ctx == NULL) {
WLOG(WS_LOG_ERROR, "No host IP set to check against!");
ret = -1;
}
if (ret == 0) {
while (current != NULL) {
if (current->type == ASN_IP_TYPE) {
WLOG(WS_LOG_DEBUG, "host cert alt. name IP : %s",
current->ipString);
WLOG(WS_LOG_DEBUG,
"\texpecting host IP : %s", (char*)ctx);
if (XSTRCMP((const char*)ctx,
current->ipString) == 0) {
WLOG(WS_LOG_DEBUG, "\tmatched!");
ipMatch = 1;
}
}
current = current->next;
}
}
if (ipMatch == 0) {
printf("IP did not match expected IP");
if (!IPOverride) {
printf("\n");
ret = -1;
}
else {
ret = 0;
printf("..overriding\n");
}
}
}
FreeDecodedCert(&dCert);
}
}
#else
WLOG(WS_LOG_DEBUG, "wolfSSL not built with OPENSSL_ALL or WOLFSSL_IP_ALT_NAME");
WLOG(WS_LOG_DEBUG, "\tnot checking IP address from peer's cert");
#endif
#endif
if (encodedKey)
WFREE(encodedKey, NULL, 0);
if (knownHosts)
WFREE(knownHosts, NULL, 0);
if (knownHostsName)
WFREE(knownHostsName, NULL, 0);
return ret;
}
int ClientUserAuth(byte authType,
WS_UserAuthData* authData,
void* ctx)
{
int ret = WOLFSSH_USERAUTH_SUCCESS;
WOLFSSH_UNUSED(ctx);
#ifdef DEBUG_WOLFSSH
/* inspect supported types from server */
printf("Server supports:\n");
if (authData->type & WOLFSSH_USERAUTH_PASSWORD) {
printf(" - password\n");
}
if (authData->type & WOLFSSH_USERAUTH_PUBLICKEY) {
printf(" - publickey\n");
}
printf("wolfSSH requesting to use type %d\n", authType);
#endif
/* Wait for request of public key on names known to have one */
if ((authData->type & WOLFSSH_USERAUTH_PUBLICKEY) &&
authData->username != NULL &&
authData->usernameSz > 0) {
/* in the case that the user passed in a public key file,
* use public key auth */
if (pubKeyLoaded == 1) {
if (authType == WOLFSSH_USERAUTH_PASSWORD) {
return WOLFSSH_USERAUTH_FAILURE;
}
}
}
if (authType == WOLFSSH_USERAUTH_PUBLICKEY) {
WS_UserAuthData_PublicKey* pk = &authData->sf.publicKey;
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) {
printf("Password: ");
fflush(stdout);
ClientSetEcho(0);
if (fgets((char*)userPassword, sizeof(userPassword), stdin) == NULL) {
fprintf(stderr, "Getting password failed.\n");
ret = WOLFSSH_USERAUTH_FAILURE;
}
else {
char* c = strpbrk((char*)userPassword, "\r\n");
if (c != NULL)
*c = '\0';
}
ClientSetEcho(1);
#ifdef USE_WINDOWS_API
printf("\r\n");
#endif
fflush(stdout);
if (ret == WOLFSSH_USERAUTH_SUCCESS) {
authData->sf.password.password = userPassword;
authData->sf.password.passwordSz =
(word32)strlen((const char*)userPassword);
}
}
return ret;
}
/* type = 2 : shell / execute command settings
* type = 0 : password
* type = 1 : restore default
* return 0 on success */
int ClientSetEcho(int type)
{
#if !defined(USE_WINDOWS_API) && !defined(MICROCHIP_PIC32)
static int echoInit = 0;
static struct termios originalTerm;
if (!echoInit) {
if (tcgetattr(STDIN_FILENO, &originalTerm) != 0) {
printf("Couldn't get the original terminal settings.\n");
return -1;
}
echoInit = 1;
}
if (type == 1) {
if (tcsetattr(STDIN_FILENO, TCSANOW, &originalTerm) != 0) {
printf("Couldn't restore the terminal settings.\n");
return -1;
}
}
else {
struct termios newTerm;
memcpy(&newTerm, &originalTerm, sizeof(struct termios));
newTerm.c_lflag &= ~ECHO;
if (type == 2) {
newTerm.c_lflag &= ~(ICANON | ECHOE | ECHOK | ECHONL | ISIG);
}
else {
newTerm.c_lflag |= ICANON;
}
if (tcsetattr(STDIN_FILENO, TCSANOW, &newTerm) != 0) {
printf("Couldn't turn off echo.\n");
return -1;
}
}
#else
static int echoInit = 0;
static DWORD originalTerm;
static CONSOLE_SCREEN_BUFFER_INFO screenOrig;
HANDLE stdinHandle = GetStdHandle(STD_INPUT_HANDLE);
if (!echoInit) {
if (GetConsoleMode(stdinHandle, &originalTerm) == 0) {
printf("Couldn't get the original terminal settings.\n");
return -1;
}
echoInit = 1;
}
if (type == 1) {
if (SetConsoleMode(stdinHandle, originalTerm) == 0) {
printf("Couldn't restore the terminal settings.\n");
return -1;
}
}
else if (type == 2) {
DWORD newTerm = originalTerm;
newTerm &= ~ENABLE_PROCESSED_INPUT;
newTerm &= ~ENABLE_PROCESSED_OUTPUT;
newTerm &= ~ENABLE_LINE_INPUT;
newTerm &= ~ENABLE_ECHO_INPUT;
newTerm &= ~(ENABLE_EXTENDED_FLAGS | ENABLE_INSERT_MODE);
if (SetConsoleMode(stdinHandle, newTerm) == 0) {
printf("Couldn't turn off echo.\n");
return -1;
}
}
else {
DWORD newTerm = originalTerm;
newTerm &= ~ENABLE_ECHO_INPUT;
if (SetConsoleMode(stdinHandle, newTerm) == 0) {
printf("Couldn't turn off echo.\n");
return -1;
}
}
#endif
return 0;
}
/* Set certificate to use and public key.
* returns 0 on success */
int ClientUseCert(const char* certName)
{
int ret = 0;
if (certName != NULL) {
#ifdef WOLFSSH_CERTS
ret = load_der_file(certName, &userPublicKey, &userPublicKeySz);
if (ret == 0) {
userPublicKeyType = publicKeyType;
userPublicKeyTypeSz = (word32)WSTRLEN((const char*)publicKeyType);
pubKeyLoaded = 1;
}
#else
fprintf(stderr, "Certificate support not compiled in");
ret = WS_NOT_COMPILED;
#endif
}
return ret;
}
/* Reads the private key to use from file name privKeyName.
* returns 0 on success */
int ClientSetPrivateKey(const char* privKeyName)
{
int ret;
userPrivateKey = NULL; /* create new buffer based on parsed input */
ret = wolfSSH_ReadKey_file(privKeyName,
(byte**)&userPrivateKey, &userPrivateKeySz,
(const byte**)&userPrivateKeyType, &userPrivateKeyTypeSz,
&isPrivate, NULL);
return ret;
}
/* Set public key to use
* returns 0 on success */
int ClientUsePubKey(const char* pubKeyName)
{
int ret;
userPublicKey = NULL; /* create new buffer based on parsed input */
ret = wolfSSH_ReadKey_file(pubKeyName,
&userPublicKey, &userPublicKeySz,
(const byte**)&userPublicKeyType, &userPublicKeyTypeSz,
&isPrivate, NULL);
if (ret == 0) {
pubKeyLoaded = 1;
}
return ret;
}
int ClientLoadCA(WOLFSSH_CTX* ctx, const char* caCert)
{
int ret = 0;
/* CA certificate to verify host cert with */
if (caCert) {
#ifdef WOLFSSH_CERTS
byte* der = NULL;
word32 derSz;
ret = load_der_file(caCert, &der, &derSz);
if (ret == 0) {
if (wolfSSH_CTX_AddRootCert_buffer(ctx, der, derSz,
WOLFSSH_FORMAT_ASN1) != WS_SUCCESS) {
fprintf(stderr, "Couldn't parse in CA certificate.");
ret = WS_PARSE_E;
}
WFREE(der, NULL, 0);
}
#else
WOLFSSH_UNUSED(ctx);
fprintf(stderr, "Support for certificates not compiled in.");
ret = WS_NOT_COMPILED;
#endif
}
return ret;
}
void ClientFreeBuffers(void)
{
if (userPublicKey != userPublicKeyBuf) {
WFREE(userPublicKey, NULL, DYNTYPE_PRIVKEY);
}
if (userPrivateKey != userPrivateKeyBuf) {
WFREE(userPrivateKey, NULL, DYNTYPE_PRIVKEY);
}
}