Add Phase 9C security hardening
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.
This commit is contained in:
@@ -0,0 +1,182 @@
|
||||
/* 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;
|
||||
}
|
||||
Reference in New Issue
Block a user