Skip to content
Open
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
255 changes: 255 additions & 0 deletions tests/unit.c
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,249 @@
#include "wolfcrypt/test/test.h"
#endif

#ifdef WOLFCRYPT_HAVE_SAKKE
#include <wolfssl/wolfcrypt/sakke.h>
#endif
#include <wolfssl/wolfcrypt/random.h>
#include <wolfssl/wolfcrypt/error-crypt.h>

#include <string.h>
#include <stdlib.h>

/* The audit_07 OOB regression test uses POSIX fork/mmap/mprotect to put
* a guard page right after the SSV buffer; on non-POSIX targets the test
* SKIPs. */
#if defined(WOLFCRYPT_HAVE_SAKKE) && \
(defined(__unix__) || defined(__APPLE__) || defined(__linux__))
#define SAKKE_OOB_TEST_HAVE_POSIX
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/mman.h>
#endif

/* Child exit codes. SETUP_FAILED tells the parent the child never reached
* the buggy code path -> SKIP, not PASS. */
#define SAKKE_OOB_CHILD_OK 0
#define SAKKE_OOB_CHILD_SETUP_FAILED 2
#define SAKKE_OOB_CHILD_MMAP_FAILED 3

#ifdef SAKKE_OOB_TEST_HAVE_POSIX
/* The trigger always _exit()s; mark noreturn for GCC/Clang to keep
* -Wmissing-noreturn quiet. The POSIX guard above already constrains us
* to compilers that understand the GNU attribute. */
#if defined(__GNUC__) || defined(__clang__)
#define SAKKE_OOB_NORETURN __attribute__((noreturn))
#else
#define SAKKE_OOB_NORETURN
#endif

/* Regression test for audit_07. Previously sakke_xor_in_v wrote hashSz
* bytes at out+idx without clamping to the bytes remaining in the
* caller's buffer, so with ssvSz=48 and SHA-256 (hashSz=32) the second
* iteration of sakke_hash_to_range overshot the 48-byte buffer by 16
* bytes. We run the call in a forked child with the buffer placed flush
* against a PROT_NONE guard page: before the fix the OOB write SIGSEGV'd
* (or SIGABRT under ASan); the fix produces a clean exit. */
static SAKKE_OOB_NORETURN void sakke_oob_trigger(void)
{
SakkeKey key;
WC_RNG rng;
unsigned char id[] = "user@example.com";
word16 ssvSz = 48;
word16 authSz = 0;
long pgsz = sysconf(_SC_PAGESIZE);
unsigned char* base;
unsigned char* ssv;
unsigned char* auth = NULL;
int ret;

base = (unsigned char*)mmap(NULL, (size_t)(2 * pgsz),
PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if (base == MAP_FAILED) _exit(SAKKE_OOB_CHILD_MMAP_FAILED);
if (mprotect(base + pgsz, (size_t)pgsz, PROT_NONE) != 0)
_exit(SAKKE_OOB_CHILD_MMAP_FAILED);
ssv = base + pgsz - ssvSz; /* ssv ends exactly at the guard page */

if (wc_InitRng(&rng) != 0) _exit(SAKKE_OOB_CHILD_SETUP_FAILED);
if (wc_InitSakkeKey_ex(&key, 128, ECC_SAKKE_1, NULL, INVALID_DEVID) != 0)
_exit(SAKKE_OOB_CHILD_SETUP_FAILED);
if (wc_MakeSakkeKey(&key, &rng) != 0)
_exit(SAKKE_OOB_CHILD_SETUP_FAILED);
if (wc_SetSakkeIdentity(&key, id, sizeof(id) - 1) != 0)
_exit(SAKKE_OOB_CHILD_SETUP_FAILED);
ret = wc_MakeSakkeEncapsulatedSSV(&key, WC_HASH_TYPE_SHA256, ssv, ssvSz,
NULL, &authSz);
if (ret != WC_NO_ERR_TRACE(LENGTH_ONLY_E))
_exit(SAKKE_OOB_CHILD_SETUP_FAILED);
auth = (unsigned char*)malloc(authSz);
if (auth == NULL) _exit(SAKKE_OOB_CHILD_SETUP_FAILED);
(void)wc_MakeSakkeEncapsulatedSSV(&key, WC_HASH_TYPE_SHA256, ssv, ssvSz,
auth, &authSz);
free(auth);
wc_FreeSakkeKey(&key);
wc_FreeRng(&rng);
_exit(SAKKE_OOB_CHILD_OK);
}

static int sakke_xor_in_v_oob_test(void)
{
pid_t pid = fork();
int status = 0;
if (pid < 0) {
printf("sakke_oob: SKIP (fork failed)\n");
return 0;
}
if (pid == 0) {
/* Silence ASan/glibc noise from the child; the parent only looks
* at the exit status. freopen() is declared warn_unused_result,
* so we capture the return into a local and (void)-cast it
* rather than discarding the call expression directly. */
FILE* dn = freopen("/dev/null", "w", stderr);
(void)dn;
sakke_oob_trigger();
_exit(SAKKE_OOB_CHILD_OK);
}
if (waitpid(pid, &status, 0) < 0) {
printf("sakke_oob: SKIP (waitpid failed)\n");
return 0;
}
if (WIFSIGNALED(status)) {
int sig = WTERMSIG(status);
if (sig == SIGSEGV || sig == SIGBUS || sig == SIGABRT) {
printf("sakke_oob: FAIL (signal %d, ssvSz=48 SHA-256)\n", sig);
return -1;
}
}
if (WIFEXITED(status)) {
int code = WEXITSTATUS(status);
if (code == SAKKE_OOB_CHILD_SETUP_FAILED ||
code == SAKKE_OOB_CHILD_MMAP_FAILED) {
printf("sakke_oob: SKIP (child setup failed, code=%d)\n", code);
return 0;
}
}
printf("sakke_oob: PASS\n");
return 0;
}
#elif defined(WOLFCRYPT_HAVE_SAKKE)
static int sakke_xor_in_v_oob_test(void)
{
printf("sakke_oob: SKIP (needs POSIX fork/mmap)\n");
return 0;
}
#else
static int sakke_xor_in_v_oob_test(void)
{
printf("sakke_oob: SKIP (WOLFCRYPT_HAVE_SAKKE not defined)\n");
return 0;
}
#endif

#ifdef WOLFCRYPT_HAVE_SAKKE
/* Verify sakke_hash_to_range produces the full RFC 6508 5.1 mask for
* ssvSz=48 with SHA-256, and that the public APIs reject ssvSz=0. The
* old broken offset math left ssv[16..31] never XORed; encapsulating
* an all-zero ssv exposes the gap (those bytes stay zero under the
* bug, become random mask material once fixed). */
static int sakke_mask_correctness_test(void)
{
SakkeKey key;
WC_RNG rng;
unsigned char id[] = "user@example.com";
word16 ssvSz = 48;
word16 ssvSzZero = 0;
word16 authSz = 0;
unsigned char ssv[48];
unsigned char* auth = NULL;
int ret;
int i;
int allZero = 1;
int rc = 0;

if (wc_InitRng(&rng) != 0) {
printf("sakke_correctness: SKIP (rng init)\n");
return 0;
}
if (wc_InitSakkeKey_ex(&key, 128, ECC_SAKKE_1, NULL, INVALID_DEVID) != 0) {
wc_FreeRng(&rng);
printf("sakke_correctness: SKIP (sakke key init)\n");
return 0;
}
if (wc_MakeSakkeKey(&key, &rng) != 0) {
wc_FreeSakkeKey(&key);
wc_FreeRng(&rng);
printf("sakke_correctness: SKIP (sakke key gen)\n");
return 0;
}
if (wc_SetSakkeIdentity(&key, id, sizeof(id) - 1) != 0) {
wc_FreeSakkeKey(&key);
wc_FreeRng(&rng);
printf("sakke_correctness: SKIP (set identity)\n");
return 0;
}

ret = wc_MakeSakkeEncapsulatedSSV(&key, WC_HASH_TYPE_SHA256, ssv, ssvSz,
NULL, &authSz);
if (ret != WC_NO_ERR_TRACE(LENGTH_ONLY_E)) {
printf("sakke_correctness: SKIP (auth size query)\n");
goto cleanup;
}
auth = (unsigned char*)malloc(authSz);
if (auth == NULL) {
printf("sakke_correctness: SKIP (alloc)\n");
goto cleanup;
}

XMEMSET(ssv, 0, ssvSz);
ret = wc_MakeSakkeEncapsulatedSSV(&key, WC_HASH_TYPE_SHA256, ssv, ssvSz,
auth, &authSz);
if (ret != 0) {
printf("sakke_correctness: FAIL (encap ret=%d)\n", ret);
rc = -1;
goto cleanup;
}
for (i = 16; i < 32; i++) {
if (ssv[i] != 0) { allZero = 0; break; }
}
if (allZero) {
printf("sakke_correctness: FAIL (ssv[16..31] not XORed)\n");
rc = -1;
goto cleanup;
}

ret = wc_MakeSakkeEncapsulatedSSV(&key, WC_HASH_TYPE_SHA256, ssv, ssvSzZero,
auth, &authSz);
if (ret != WC_NO_ERR_TRACE(BAD_FUNC_ARG)) {
printf("sakke_correctness: FAIL (encap ssvSz=0 ret=%d)\n", ret);
rc = -1;
goto cleanup;
}
ret = wc_DeriveSakkeSSV(&key, WC_HASH_TYPE_SHA256, ssv, ssvSzZero,
auth, authSz);
if (ret != WC_NO_ERR_TRACE(BAD_FUNC_ARG)) {
printf("sakke_correctness: FAIL (derive ssvSz=0 ret=%d)\n", ret);
rc = -1;
goto cleanup;
}

printf("sakke_correctness: PASS\n");

cleanup:
if (auth != NULL) free(auth);
wc_FreeSakkeKey(&key);
wc_FreeRng(&rng);
return rc;
}
#else
static int sakke_mask_correctness_test(void)
{
printf("sakke_correctness: SKIP (WOLFCRYPT_HAVE_SAKKE not defined)\n");
return 0;
}
#endif

int allTesting = 1;
int apiTesting = 1;
int myoptind = 0;
Expand Down Expand Up @@ -260,6 +503,18 @@ int unit_test(int argc, char** argv)
goto exit;
}

if (sakke_xor_in_v_oob_test() != 0) {
ret = -1;
fflush(stdout);
goto exit;
}
if (sakke_mask_correctness_test() != 0) {
ret = -1;
fflush(stdout);
goto exit;
}
fflush(stdout);

XMEMSET(&wc_args, 0, sizeof(wc_args));
wolfcrypt_test(&wc_args);
if (wc_args.return_code != 0) {
Expand Down
57 changes: 41 additions & 16 deletions wolfcrypt/src/sakke.c
Original file line number Diff line number Diff line change
Expand Up @@ -6162,20 +6162,30 @@ static int sakke_calc_h_v(SakkeKey* key, enum wc_HashType hashType,
static void sakke_xor_in_v(const byte* v, word32 hashSz, byte* out, word32 idx,
word32 n)
{
int o;
word32 i;
word32 skip;
word32 off;
word32 len;

/* RFC 6508 5.1: output is the low n octets of v_1||v_2||...||v_l (the
* concatenation of l hash outputs taken modulo 2^(n*8)). When n is not
* a multiple of hashSz, drop the leading 'skip' high bytes of the first
* hash output. */
skip = n % hashSz;
skip = (skip == 0) ? 0 : (hashSz - skip);

if (idx == 0) {
i = hashSz - (n % hashSz);
if (i == hashSz) {
i = 0;
}
off = 0;
len = hashSz - skip;
xorbuf(out + off, v + skip, len);
}
else {
i = 0;
off = idx - skip;
len = n - off;
if (len > hashSz) {
len = hashSz;
}
xorbuf(out + off, v, len);
}
o = (int)i;
xorbuf(out + idx + i - o, v + i, hashSz - i);
}

/*
Expand Down Expand Up @@ -6682,11 +6692,12 @@ static int sakke_compute_point_r(SakkeKey* key, const byte* id, word16 idSz,
* @param [out] auth Authentication data.
* @param [out] authSz Size of authentication data in bytes.
* @return 0 on success.
* @return BAD_FUNC_ARG when key, ssv or encSz is NULL, ssvSz is to big or
* encSz is too small.
* @return BAD_FUNC_ARG when key, ssv or authSz is NULL, when encapsulating
* and ssvSz is 0 or larger than the curve modulus, or *authSz is
* too small.
* @return BAD_STATE_E when identity not set.
* @return LENGTH_ONLY_E when auth is NULL. authSz contains required size of
* auth in bytes.
* auth in bytes. ssvSz is not consulted on the size-query path.
* @return MEMORY_E when dynamic memory allocation fails.
* @return Other -ve value on internal failure.
*/
Expand Down Expand Up @@ -6718,8 +6729,17 @@ int wc_MakeSakkeEncapsulatedSSV(SakkeKey* key, enum wc_HashType hashType,
/* Uncompressed point */
outSz = (word16)(1 + 2 * n);

if ((auth != NULL) && (*authSz < outSz)) {
err = BAD_FUNC_ARG;
/* ssvSz is only meaningful when actually encapsulating; the
* size-query path (auth == NULL) only depends on the curve. RFC
* 6508 6.2.1 step 1 puts SSV in the range 0..2^n-1, so ssvSz must
* be in [1, n] octets. */
if (auth != NULL) {
if ((ssvSz == 0) || (ssvSz > n)) {
err = BAD_FUNC_ARG;
}
else if (*authSz < outSz) {
err = BAD_FUNC_ARG;
}
}
}
if (err == 0) {
Expand Down Expand Up @@ -6857,7 +6877,8 @@ int wc_GenerateSakkeSSV(SakkeKey* key, WC_RNG* rng, byte* ssv, word16* ssvSz)
* @param [in] auth Authentication data.
* @param [in] authSz Size of authentication data in bytes.
* @return 0 on success.
* @return BAD_FUNC_ARG when key, ssv or auth is NULL.
* @return BAD_FUNC_ARG when key, ssv or auth is NULL, ssvSz is 0 or
* larger than the curve modulus byte length.
* @return BAD_STATE_E when RSK or identity not set.
* @return SAKKE_VERIFY_FAIL_E when calculated R doesn't match the encapsulated
* data's R.
Expand All @@ -6876,7 +6897,7 @@ int wc_DeriveSakkeSSV(SakkeKey* key, enum wc_HashType hashType, byte* ssv,
byte* test = NULL;
byte a[WC_MAX_DIGEST_SIZE] = {0};

if ((key == NULL) || (ssv == NULL) || (auth == NULL)) {
if ((key == NULL) || (ssv == NULL) || (auth == NULL) || (ssvSz == 0)) {
err = BAD_FUNC_ARG;
}
if ((err == 0) && (!key->rsk.set || (key->idSz == 0))) {
Expand All @@ -6895,6 +6916,10 @@ int wc_DeriveSakkeSSV(SakkeKey* key, enum wc_HashType hashType, byte* ssv,
if (authSz != 2 * n + 1) {
err = BAD_FUNC_ARG;
}
/* RFC 6508 6.2.1: SSV is in 0..2^n-1, so ssvSz must be <= n. */
else if (ssvSz > n) {
err = BAD_FUNC_ARG;
}
}
if (err == 0) {
err = sakke_load_base_point(key);
Expand Down
Loading