Generate exact-hash SDK source overrides without modifying dependencies. Harden SSH allocation and algorithm policy, tighten web authentication cleanup, and add focused host contract tests and documentation.
183 lines
6.3 KiB
C
183 lines
6.3 KiB
C
/* Actual create_context() + actual policy; identity/vendor/callback doubles. */
|
|
#include <assert.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include "ssh_protocol_policy.h"
|
|
#include "context_constants.h"
|
|
|
|
typedef int esp_err_t;
|
|
enum { ESP_OK = 0, ESP_FAIL = -1, ESP_ERR_NO_MEM = 0x101 };
|
|
static WOLFSSH_CTX candidate, *s_context;
|
|
static unsigned copies, news, imports, wipes, setters, callbacks, frees;
|
|
static unsigned fail_setter;
|
|
static int copy_error, allocation_fail, import_error, setter_error;
|
|
static unsigned char *identity;
|
|
static size_t identity_capacity;
|
|
|
|
static void unpublished(void)
|
|
{
|
|
assert(s_context == NULL);
|
|
}
|
|
static void wiped(void)
|
|
{
|
|
assert(wipes == 1);
|
|
assert(identity_capacity == SSH_SECURITY_PRIVATE_KEY_DER_CAPACITY);
|
|
/* Only called while the extracted create_context stack frame is alive. */
|
|
for (size_t i = 0; i < identity_capacity; ++i) assert(identity[i] == 0);
|
|
}
|
|
static esp_err_t ssh_security_copy_private_key(unsigned char *out, size_t capacity,
|
|
size_t *length)
|
|
{
|
|
unpublished();
|
|
assert(++copies == 1);
|
|
assert(capacity == SSH_SECURITY_PRIVATE_KEY_DER_CAPACITY);
|
|
for (size_t i = 0; i < capacity; ++i) assert(out[i] == 0);
|
|
identity = out;
|
|
identity_capacity = capacity;
|
|
/* Mark the unused tail too, so a short wipe cannot pass this test. */
|
|
memset(out, 0x6d, capacity);
|
|
*length = 31;
|
|
return copy_error;
|
|
}
|
|
static void secure_wipe(void *ptr, size_t length)
|
|
{
|
|
unpublished();
|
|
assert(ptr == identity && length == identity_capacity);
|
|
assert(wipes++ == 0);
|
|
memset(ptr, 0, length);
|
|
}
|
|
static WOLFSSH_CTX *wolfSSH_CTX_new(int endpoint, void *heap)
|
|
{
|
|
unpublished();
|
|
assert(copies == 1 && wipes == 0 && imports == 0);
|
|
assert(endpoint == WOLFSSH_ENDPOINT_SERVER && heap == NULL);
|
|
assert(++news == 1);
|
|
return allocation_fail ? NULL : &candidate;
|
|
}
|
|
static int wolfSSH_CTX_UsePrivateKey_buffer(WOLFSSH_CTX *ctx,
|
|
const unsigned char *key, word32 length, int format)
|
|
{
|
|
unpublished();
|
|
assert(ctx == &candidate && news == 1 && wipes == 0);
|
|
assert(++imports == 1);
|
|
assert(key == identity && length == 31 && format == WOLFSSH_FORMAT_ASN1);
|
|
for (size_t i = 0; i < identity_capacity; ++i) assert(key[i] == 0x6d);
|
|
return import_error;
|
|
}
|
|
static void wolfSSH_CTX_free(WOLFSSH_CTX *ctx)
|
|
{
|
|
unpublished();
|
|
wiped();
|
|
assert(ctx == &candidate && news == 1 && callbacks == 0);
|
|
assert(++frees == 1);
|
|
}
|
|
static int set_list(WOLFSSH_CTX *ctx, const char *list, const char **field,
|
|
unsigned step)
|
|
{
|
|
unpublished();
|
|
wiped();
|
|
assert(imports == 1 && import_error == 0 && frees == 0 && callbacks == 0);
|
|
assert(ctx == &candidate && ++setters == step);
|
|
assert(list != NULL && list[0] != '\0');
|
|
if (step == fail_setter) return setter_error;
|
|
*field = list;
|
|
return WS_SUCCESS;
|
|
}
|
|
#define SETTER(name, field, step) \
|
|
int wolfSSH_CTX_SetAlgoList##name(WOLFSSH_CTX *ctx, const char *list) \
|
|
{ return set_list(ctx, list, &ctx->field, step); }
|
|
SETTER(Kex, algoListKex, 1)
|
|
SETTER(Key, algoListKey, 2)
|
|
SETTER(Cipher, algoListCipher, 3)
|
|
SETTER(Mac, algoListMac, 4)
|
|
SETTER(KeyAccepted, algoListKeyAccepted, 5)
|
|
|
|
static void bounded_ssh_receive(void) {}
|
|
static void authenticate_user(void) {}
|
|
static void allowed_auth_types(void) {}
|
|
static void authentication_result(void) {}
|
|
static void reject_keyboard_auth(void) {}
|
|
static void accept_shell(void) {}
|
|
static void reject_channel_request(void) {}
|
|
static int callback(WOLFSSH_CTX *ctx, void (*actual)(void),
|
|
void (*expected)(void), unsigned step)
|
|
{
|
|
unpublished();
|
|
wiped();
|
|
assert(ctx == &candidate && setters == 5 && fail_setter == 0 && frees == 0);
|
|
assert(++callbacks == step && actual == expected);
|
|
return WS_SUCCESS;
|
|
}
|
|
#define CALLBACK(name, expected, step) \
|
|
static int name(WOLFSSH_CTX *ctx, void (*cb)(void)) \
|
|
{ return callback(ctx, cb, expected, step); }
|
|
CALLBACK(wolfSSH_SetIORecv, bounded_ssh_receive, 1)
|
|
CALLBACK(wolfSSH_SetUserAuth, authenticate_user, 2)
|
|
CALLBACK(wolfSSH_SetUserAuthTypes, allowed_auth_types, 3)
|
|
CALLBACK(wolfSSH_SetUserAuthResult, authentication_result, 4)
|
|
CALLBACK(wolfSSH_SetKeyboardAuthPrompts, reject_keyboard_auth, 5)
|
|
CALLBACK(wolfSSH_CTX_SetChannelReqShellCb, accept_shell, 6)
|
|
CALLBACK(wolfSSH_CTX_SetChannelReqExecCb, reject_channel_request, 7)
|
|
CALLBACK(wolfSSH_CTX_SetChannelReqSubsysCb, reject_channel_request, 8)
|
|
|
|
#include "context_actual.c"
|
|
|
|
static void reset(void)
|
|
{
|
|
memset(&candidate, 0, sizeof(candidate));
|
|
s_context = NULL;
|
|
copies = news = imports = wipes = setters = callbacks = frees = 0;
|
|
fail_setter = 0;
|
|
copy_error = allocation_fail = import_error = setter_error = 0;
|
|
identity = NULL;
|
|
identity_capacity = 0;
|
|
}
|
|
static void failed(int expected)
|
|
{
|
|
assert(create_context() == expected);
|
|
assert(s_context == NULL && callbacks == 0 && wipes == 1 && copies == 1);
|
|
/* identity points at a retired stack frame now: never inspect it here. */
|
|
identity = NULL;
|
|
}
|
|
int main(void)
|
|
{
|
|
unsigned cases = 0;
|
|
reset();
|
|
copy_error = 0x4321;
|
|
failed(copy_error);
|
|
assert(news == 0 && imports == 0 && setters == 0 && frees == 0);
|
|
++cases;
|
|
reset();
|
|
allocation_fail = 1;
|
|
failed(ESP_ERR_NO_MEM);
|
|
assert(news == 1 && imports == 0 && setters == 0 && frees == 0);
|
|
++cases;
|
|
for (unsigned sign = 0; sign < 2; ++sign) {
|
|
reset();
|
|
import_error = sign ? 7001 : -7001;
|
|
failed(ESP_FAIL);
|
|
assert(news == 1 && imports == 1 && setters == 0 && frees == 1);
|
|
++cases;
|
|
}
|
|
for (unsigned step = 1; step <= 5; ++step) {
|
|
for (unsigned sign = 0; sign < 2; ++sign) {
|
|
reset();
|
|
fail_setter = step;
|
|
setter_error = sign ? (int)(8000 + step) : -(int)(8000 + step);
|
|
failed(ESP_FAIL);
|
|
assert(news == 1 && imports == 1 && setters == step && frees == 1);
|
|
++cases;
|
|
}
|
|
}
|
|
reset();
|
|
assert(create_context() == ESP_OK);
|
|
identity = NULL;
|
|
assert(s_context == &candidate);
|
|
assert(copies == 1 && news == 1 && imports == 1 && wipes == 1);
|
|
assert(setters == 5 && callbacks == 8 && frees == 0);
|
|
++cases;
|
|
assert(cases == 15);
|
|
puts("PASS: actual create_context + policy: 15 cases, full identity wipe before policy/callbacks/free, exact cleanup, publication only after success");
|
|
return 0;
|
|
}
|