Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 26 additions & 3 deletions .github/workflows/sshd-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,25 @@ jobs:
matrix:
os: [ ubuntu-latest ]
wolfssl: ${{ fromJson(needs.create_matrix.outputs['versions']) }}
mldsa: [ 'yes', 'no' ]
# The last two variants reuse the ML-DSA-enabled wolfSSL and build
# wolfSSH with extra defines it has no configure option for: one
# turns the composite algorithms off, one takes the small-stack
# (heap-allocated) composite paths. Those legs stop after the sshd
# tests; the later steps reconfigure without the extra define.
mldsa: [ 'yes', 'no', 'yes-no-composites', 'yes-small-stack' ]
include:
- mldsa: 'yes'
wolfssl_mldsa: 'yes'
extra_flags: ''
- mldsa: 'no'
wolfssl_mldsa: 'no'
extra_flags: ''
- mldsa: 'yes-no-composites'
wolfssl_mldsa: 'yes'
extra_flags: ' -DWOLFSSH_NO_MLDSA_COMPOSITES'
- mldsa: 'yes-small-stack'
wolfssl_mldsa: 'yes'
extra_flags: ' -DWOLFSSH_SMALL_STACK'
name: Build and test the wolfsshd and wolfssh apps
runs-on: ${{ matrix.os }}
timeout-minutes: 10
Expand All @@ -80,7 +98,7 @@ jobs:
uses: actions/cache@v5
with:
path: build-dir/
key: wolfssh-sshd-wolfssl-${{ matrix.wolfssl }}-${{ matrix.os }}-mldsa-${{ matrix.mldsa }}-v3
key: wolfssh-sshd-wolfssl-${{ matrix.wolfssl }}-${{ matrix.os }}-mldsa-${{ matrix.wolfssl_mldsa }}-v3
fail-on-cache-miss: true

- uses: actions/checkout@v6
Expand All @@ -94,7 +112,7 @@ jobs:
- name: configure
working-directory: ./wolfssh/
run : |
./configure --enable-all --enable-ossh-certs LDFLAGS="-L${{ github.workspace }}/build-dir/lib" CPPFLAGS="-I${{ github.workspace }}/build-dir/include -DWOLFSSH_NO_FPKI -DWOLFSSH_NO_SFTP_TIMEOUT -DWOLFSSH_MAX_SFTP_RW=4000000 -DMAX_PATH_SZ=120"
./configure --enable-all --enable-ossh-certs LDFLAGS="-L${{ github.workspace }}/build-dir/lib" CPPFLAGS="-I${{ github.workspace }}/build-dir/include -DWOLFSSH_NO_FPKI -DWOLFSSH_NO_SFTP_TIMEOUT -DWOLFSSH_MAX_SFTP_RW=4000000 -DMAX_PATH_SZ=120${{ matrix.extra_flags }}"

- name: make check
working-directory: ./wolfssh/
Expand All @@ -109,6 +127,7 @@ jobs:
# could use optimization with caching
- name: Test memory after close down
working-directory: ./wolfssh/
if: matrix.extra_flags == ''
run: |
sudo apt-get -y update
sudo apt-get -y install valgrind
Expand All @@ -123,6 +142,7 @@ jobs:
# regression test, check that cat command does not hang
- name: Test cat command for hanging
working-directory: ./wolfssh/
if: matrix.extra_flags == ''
timeout-minutes: 1
run: |
touch sshd_config.txt
Expand All @@ -145,16 +165,19 @@ jobs:

- name: configure with debug
working-directory: ./wolfssh/
if: matrix.extra_flags == ''
run : |
./configure --enable-all --enable-debug LDFLAGS="-L${{ github.workspace }}/build-dir/lib" CPPFLAGS="-I${{ github.workspace }}/build-dir/include -DWOLFSSH_NO_FPKI -DWOLFSSH_NO_SFTP_TIMEOUT -DWOLFSSH_MAX_SFTP_RW=4000000 -DMAX_PATH_SZ=120"

- name: make
working-directory: ./wolfssh/
if: matrix.extra_flags == ''
run: make

# ssh_kex_algos.sh requires debug output otherwise it is skipped
- name: Run wolfSSHd tests with debug
working-directory: ./wolfssh/apps/wolfsshd/test
if: matrix.extra_flags == ''
run: |
git log -3
sudo ./run_all_sshd_tests.sh --match ssh_kex_algos.sh
Expand Down
7 changes: 4 additions & 3 deletions apps/wolfssh-options.c
Original file line number Diff line number Diff line change
Expand Up @@ -147,9 +147,10 @@ int main(void)
#ifndef WOLFSSH_NO_MLDSA
printf("MLDSA\n");
#endif
/* Same guard as cannedKeyAlgoNamesHostKey in src/internal.c: the
* composite needs the ECDSA half too. */
#if !defined(WOLFSSH_NO_MLDSA87) && \
/* Same guard as cannedKeyAlgoNamesHostKey in src/internal.c: composites
* as a whole can be compiled out, and this one needs the ECDSA half. */
#if !defined(WOLFSSH_NO_MLDSA_COMPOSITES) && \
!defined(WOLFSSH_NO_MLDSA87) && \
!defined(WOLFSSH_NO_ECDSA_SHA2_NISTP384) && !defined(NO_SHA512)
printf("MLDSA87_ES384\n");
#endif
Expand Down
97 changes: 62 additions & 35 deletions apps/wolfsshd/auth.c
Original file line number Diff line number Diff line change
Expand Up @@ -253,13 +253,33 @@ USER_NODE* AddNewUser(USER_NODE* list, byte type, const byte* username,
}
#endif

/* Big-endian uint32 read. ato32() is WOLFSSH_LOCAL, so it is unresolvable
* in a NO_INLINE build linked against a shared libwolfssh. */
static word32 AuthReadU32(const byte* c)
{
return ((word32)c[0] << 24) | ((word32)c[1] << 16) |
((word32)c[2] << 8) | (word32)c[3];
}

/* Maps signature algorithms to key types (e.g. RSA SHA-2 to ssh-rsa). */
static const char* AuthKeysTokenKeyType(const char* type)
{
if (WSTRCMP(type, "rsa-sha2-256") == 0 ||
WSTRCMP(type, "rsa-sha2-512") == 0) {
return "ssh-rsa";
}

return type;
}

/* TODO: Can use wolfSSH_ReadKey_buffer? */
/* isCert skips the wire-format type/embedded-type cross-check. */
#ifdef WOLFSSHD_UNIT_TEST
int CheckAuthKeysLine(char* line, word32 lineSz, const byte* key,
word32 keySz)
word32 keySz, int isCert)
#else
static int CheckAuthKeysLine(char* line, word32 lineSz, const byte* key,
word32 keySz)
word32 keySz, int isCert)
#endif
{
int ret = WSSHD_AUTH_SUCCESS;
Expand All @@ -270,41 +290,22 @@ static int CheckAuthKeysLine(char* line, word32 lineSz, const byte* key,
word32 keyCandSz = 0;
char* last = NULL;

/* Valid key types come from the same TYPE_KEY name registry
* (NameIdMap) that KEX negotiation uses, via wolfSSH_QueryKey(),
* instead of a separately hand-maintained list that could drift
* out of sync with it. */
int typeOk = 0;
word32 queryIdx = 0;
const char* algoName;

if (line == NULL || lineSz == 0 || key == NULL || keySz == 0) {
ret = WS_BAD_ARGUMENT;
}

if (ret == WSSHD_AUTH_SUCCESS) {
/* Skip truncated or whitespace-only lines. */
if ((type = WSTRTOK(line, " ", &last)) == NULL) {
ret = WS_FATAL_ERROR;
ret = WSSHD_AUTH_FAILURE;
}
else if ((keyCandBase64 = WSTRTOK(NULL, " ", &last)) == NULL) {
ret = WS_FATAL_ERROR;
ret = WSSHD_AUTH_FAILURE;
}
}
if (ret == WSSHD_AUTH_SUCCESS) {
while ((algoName = wolfSSH_QueryKey(&queryIdx)) != NULL) {
/* OpenSSH cert types are verified via the CA path, not by
* literal comparison here; exclude them. */
if (WSTRSTR(algoName, "-cert-v01@openssh.com") != NULL) {
continue;
}
if (WSTRCMP(type, algoName) == 0) {
typeOk = 1;
break;
}
}
if (!typeOk) {
/* Skip unsupported key types so the scan continues to later
* entries instead of aborting the whole file. */
/* Cert types are verified via CA path, skip literal comparison. */
if (WSTRSTR(type, "-cert-v01@openssh.com") != NULL) {
ret = WSSHD_AUTH_FAILURE;
}
}
Expand All @@ -318,10 +319,34 @@ static int CheckAuthKeysLine(char* line, word32 lineSz, const byte* key,
else {
if (Base64_Decode((byte*)keyCandBase64, keyCandBase64Sz, keyCand,
&keyCandSz) != 0) {
ret = WS_FATAL_ERROR;
/* Skip non-base64 tokens (e.g. option-prefixed lines). */
ret = WSSHD_AUTH_FAILURE;
Comment thread
stenslae marked this conversation as resolved.
}
}
}
if (ret == WSSHD_AUTH_SUCCESS && !isCert) {
/* Skip cross-check for raw DER certificate blobs. */
word32 typeStrSz;
const char* keyType = AuthKeysTokenKeyType(type);
word32 keyTypeSz = (word32)XSTRLEN(keyType);

if (keyCandSz >= 4) {
typeStrSz = AuthReadU32(keyCand);
if (typeStrSz != keyTypeSz || typeStrSz > keyCandSz - 4 ||
XMEMCMP(keyType, keyCand + 4, keyTypeSz) != 0) {
/* Skip: token type doesn't match embedded key blob type. */
wolfSSH_Log(WS_LOG_DEBUG, "[SSHD] Skipping key line, type %s "
"does not match the type embedded in this line's key "
"blob", type);
ret = WSSHD_AUTH_FAILURE;
}
}
else {
wolfSSH_Log(WS_LOG_DEBUG,
"[SSHD] Skipping key line, blob too short for a type field");
ret = WSSHD_AUTH_FAILURE;
}
}
if (ret == WSSHD_AUTH_SUCCESS) {
/* Constant-time compare to avoid leaking which prefix bytes of an
* authorized key match a candidate offered by a remote peer. */
Expand Down Expand Up @@ -1331,11 +1356,11 @@ int wolfSSHD_OpenSecureFile(const char* path, WUID_T ownerUid,
#endif
}

/* Scan a resolved keys file (authorized_keys or TrustedUserCAKeys) for
* (key, keySz). Fails closed with WSSHD_AUTH_FAILURE when no line matches.
* strictModes opens through the secure gate; the file must be owned by uid. */
/* Scan keys file. Fails closed on no match.
* strictModes requires uid ownership. isCert flags DER vs wire-format. */
static int SearchKeysFile(const char* keysFilePath, const byte* key,
word32 keySz, WUID_T uid, int strictModes)
word32 keySz, WUID_T uid, int strictModes,
int isCert)
{
int ret = WSSHD_AUTH_SUCCESS;
WFILE *f = WBADFILE;
Expand Down Expand Up @@ -1389,7 +1414,7 @@ static int SearchKeysFile(const char* keysFilePath, const byte* key,
continue; /* commented out line */
}

rc = CheckAuthKeysLine(current, currentSz, key, keySz);
rc = CheckAuthKeysLine(current, currentSz, key, keySz, isCert);
if (rc == WSSHD_AUTH_SUCCESS) {
foundKey = 1;
break;
Expand Down Expand Up @@ -1514,7 +1539,8 @@ WOLFSSHD_STATIC int SearchForPubKey(const char* path,

if (ret == WSSHD_AUTH_SUCCESS) {
ret = SearchKeysFile(authKeysPath, pubKeyCtx->publicKey,
pubKeyCtx->publicKeySz, uid, strictModes);
pubKeyCtx->publicKeySz, uid, strictModes,
pubKeyCtx->isCert);
}

return ret;
Expand Down Expand Up @@ -1571,7 +1597,7 @@ static int OsshCertCheckPrincipal(const WS_UserAuthData_PublicKey* pubKeyCtx,

nameSz = (word32)WSTRLEN(name);
while (idx + UINT32_SZ <= sz) {
ato32(p + idx, &entSz);
entSz = AuthReadU32(p + idx);
idx += UINT32_SZ;
if (entSz > sz - idx) {
break; /* malformed principals region */
Expand Down Expand Up @@ -1846,7 +1872,8 @@ static int CheckPublicKeyUnix(const char* name,
* anchor, so it is always secure-gated, regardless of StrictModes. */
if (ret == WSSHD_AUTH_SUCCESS) {
ret = SearchKeysFile(usrCaKeysFile, pubKeyCtx->caKey,
pubKeyCtx->caKeySz, geteuid(), 1 /* strictModes */);
pubKeyCtx->caKeySz, geteuid(), 1 /* strictModes */,
0 /* isCert: caKey is a wire-format key, not a cert */);
}

/* Bind the certificate to the requested user via its principals. */
Expand Down
3 changes: 2 additions & 1 deletion apps/wolfsshd/auth.h
Original file line number Diff line number Diff line change
Expand Up @@ -170,8 +170,9 @@ void DoFakePasswordCheck(WS_UserAuthData* authData);
void wolfSSHD_ResetFakePasswordCheckCountForTest(void);
int wolfSSHD_GetFakePasswordCheckCountForTest(void);
#endif
/* isCert: skips wire-format cross-check for DER certs. */
int CheckAuthKeysLine(char* line, word32 lineSz, const byte* key,
word32 keySz);
word32 keySz, int isCert);
int ResolveAuthKeysPath(const char* homeDir, const char* pattern,
const char* user, char* resolved);
int CAKeysFileDiffers(const char* a, const char* b);
Expand Down
Loading
Loading