Skip to content

Commit

Permalink
tests/pbkdf2: de-flanky-fy test
Browse files Browse the repository at this point in the history
Previously, the test vectors were encoded into the python test scripts,
converted to base64, and send over to the device under test via stdio.
The application sent back the output after converting it to base64
first, which was read back in by the test script and decoded. Finally,
the test script compared the result with the expected result.

This made the test complex, slow and, flanky, as stdio on interfaces
such as UART has a high bit error rate and some quirks (e.g. the EDBG
UART bridge e.g. in the samr21-xpro dropping bytes when bursts of more
than 64 bytes at a time are send).

This basically rewrites the test to embed the test vectors in the
firmware and do the comparison on the devices. This fixes test failures
on the samr21-xpro, the nRF52840-DK and likely many others. Also, it
is now fast.
  • Loading branch information
maribu committed Nov 21, 2022
1 parent b7dd815 commit 2f6dd98
Show file tree
Hide file tree
Showing 6 changed files with 100 additions and 287 deletions.
15 changes: 1 addition & 14 deletions tests/pbkdf2/Makefile
Original file line number Diff line number Diff line change
@@ -1,19 +1,6 @@
include ../Makefile.tests_common

# This application uses getchar and thus expects input from stdio
USEMODULE += stdin
USEMODULE += hashes
USEMODULE += base64

# Use a terminal that does not introduce extra characters into the stream.
RIOT_TERMINAL ?= socat

#ensure the rx buffer has some room even with large test patterns
CFLAGS += -DSTDIO_UART_RX_BUFSIZE=128
USEMODULE += fmt

include $(RIOTBASE)/Makefile.include

# Increase Stack size for AVR
ifneq (,$(filter avr8_common,$(USEMODULE)))
CFLAGS += -DTHREAD_STACKSIZE_MAIN=THREAD_STACKSIZE_LARGE
endif
186 changes: 79 additions & 107 deletions tests/pbkdf2/main.c
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
/*
* Copyright (C) 2019 Freie Universität Berlin.
* 2022 Otto-von-Guericke-Universität Magdeburg
*
* This file is subject to the terms and conditions of the GNU Lesser
* General Public License v2.1. See the file LICENSE in the top level
Expand All @@ -13,132 +14,103 @@
* @brief Test PBKDF2-sha256 implementation.
*
* @author Juan Carrano <j.carrano@fu-berlin.de>
* @author Marian Buschsieweke <marian.buschsieweke@ovgu.de>
*
* This application reads (password, salt, iterations) tuples from the
* standard input and outputs the derived key.
*
* The salt must be base64 encoded. The key is printed as base64.
* @}
*/

#include <assert.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>

#include "base64.h"
#include "fmt.h"
#include "hashes/pbkdf2.h"
#include "hashes/sha256.h"
#include "kernel_defines.h"

const char error_message[] = "{error}";
const char input_message[] = "{ready}";

#define LINEBUF_SZ (128)
static uint8_t key[SHA256_DIGEST_LENGTH];

enum TEST_STATE {
TEST_READ_PASS,
TEST_READ_SALT,
TEST_READ_ITERS,
TEST_COMPUTE,
TEST_ERROR
struct testcase {
const char *password;
const char *salt;
uint16_t iterations;
const uint8_t digest[sizeof(key)];
};

static void _clear_input(void)
{
/* clear input buffer */
int c;
while ( (c = getchar()) != '\n' && c != EOF ) { }
}
struct testcase testcases[] = {
{
.password = "passwd",
.salt = "salt",
.iterations = 1,
/* dig = hashlib.pbkdf2_hmac("sha256", "passwd".encode("utf-8"),
* "salt".encode("utf-8"), 1)
* "".join("0x{:02x}, ".format(b) for b in dig)
*/
.digest = {
0x55, 0xac, 0x04, 0x6e, 0x56, 0xe3, 0x08, 0x9f,
0xec, 0x16, 0x91, 0xc2, 0x25, 0x44, 0xb6, 0x05,
0xf9, 0x41, 0x85, 0x21, 0x6d, 0xde, 0x04, 0x65,
0xe6, 0x8b, 0x9d, 0x57, 0xc2, 0x0d, 0xac, 0xbc,
}
},
{
.password = "RIOT",
.salt = "rocks",
.iterations = 16,
/* dig = hashlib.pbkdf2_hmac("sha256", "RIOT".encode("utf-8"),
* "rocks".encode("utf-8"), 16)
* "".join("0x{:02x}, ".format(b) for b in dig)
*/
.digest = {
0x72, 0xa6, 0x06, 0xbb, 0x5c, 0xbe, 0x92, 0x4a,
0xd2, 0x0a, 0xee, 0xc2, 0x4e, 0xa5, 0x17, 0xc4,
0xd7, 0xb1, 0x1d, 0x04, 0x9d, 0x84, 0xbb, 0x29,
0x6b, 0x36, 0xad, 0x90, 0x4d, 0x6f, 0x79, 0xdf,
}
},
{
.password = "This is a secure password", /* <-- no it is NOT! */
.salt = "and this salt is even more secure",
.iterations = 13,
/* dig = hashlib.pbkdf2_hmac("sha256",
* "This is a secure password".encode("utf-8"),
* "and this salt is even more secure".encode("utf-8"),
* 13)
* "".join("0x{:02x}, ".format(b) for b in dig)
*/
.digest = {
0x9a, 0x41, 0x83, 0x2b, 0x77, 0xc4, 0x61, 0x64,
0x06, 0xd3, 0x2e, 0x97, 0x06, 0x5e, 0xc5, 0xc7,
0xe1, 0xa0, 0x18, 0x75, 0x01, 0xfe, 0xb8, 0xc8,
0x70, 0x92, 0x28, 0x0e, 0x1d, 0x1a, 0x00, 0xb6,
}
},
};

int main(void)
{
static char linebuf[LINEBUF_SZ];

/* There will be a few bytes wasted here */
static char password[LINEBUF_SZ];
static uint8_t salt[LINEBUF_SZ];
static uint8_t key[PBKDF2_KEY_SIZE];

size_t passwd_len = 0, salt_len = 0;
int iterations = 0;

enum TEST_STATE state = TEST_READ_PASS;

_clear_input();

while ((puts(input_message), fgets(linebuf, LINEBUF_SZ, stdin) != NULL)) {
char *s_end;
int conversion_status, line_len = strlen(linebuf)-1;
size_t b64_buff_size;

linebuf[line_len] = '\0';

switch (state) {
case TEST_READ_PASS:
strcpy(password, linebuf);
passwd_len = line_len;
state++;

break;
case TEST_READ_SALT:
/* work around bug in base64_decode */
if (line_len == 0) {
salt_len = 0;
conversion_status = BASE64_SUCCESS;
} else {
salt_len = sizeof(salt);
conversion_status = base64_decode((uint8_t*)linebuf,
line_len+1,
salt, &salt_len);
}

if (conversion_status == BASE64_SUCCESS) {
state++;
} else {
state = TEST_ERROR;
}

break;
case TEST_READ_ITERS:
iterations = strtol(linebuf, &s_end, 10);

if (*s_end != '\0') {
state = TEST_ERROR;
} else {
state++;
}

break;
default:
assert(1);
break;
bool failed = false;
for (size_t i = 0; i < ARRAY_SIZE(testcases); i++) {
struct testcase *tc = &testcases[i];
size_t password_len = strlen(tc->password);
size_t salt_len = strlen(tc->salt);
memset(key, 0x00, sizeof(key));
pbkdf2_sha256(tc->password, password_len, tc->salt, salt_len,
tc->iterations, key);

if (memcmp(tc->digest, key, sizeof(key)) != 0) {
failed = true;
print_str("Test vector ");
print_u32_dec((uint32_t)i);
print_str(": FAILED\n");
}

switch (state) {
case TEST_COMPUTE:
pbkdf2_sha256((uint8_t*)password, passwd_len, salt, salt_len,
iterations, key);

b64_buff_size = sizeof(linebuf);
conversion_status = base64_encode(key, sizeof(key),
(uint8_t*)linebuf,
&b64_buff_size);

if (conversion_status == BASE64_SUCCESS) {
linebuf[b64_buff_size] = 0;
puts(linebuf);
} else {
puts(error_message);
}
}

state = TEST_READ_PASS;
break;
case TEST_ERROR:
puts(error_message);
state = TEST_READ_PASS;
break;
default:
break;
}
if (!failed) {
print_str("TEST PASSED\n");
}

return 0;
Expand Down
42 changes: 0 additions & 42 deletions tests/pbkdf2/tests/01-rfc.py

This file was deleted.

20 changes: 20 additions & 0 deletions tests/pbkdf2/tests/01-run.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
#!/usr/bin/env python3

# Copyright (C) 2020 Otto-von-Guericke-Universität Magdeburg
#
# This file is subject to the terms and conditions of the GNU Lesser
# General Public License v2.1. See the file LICENSE in the top level
# directory for more details.

# @author Marian Buschsieweke <marian.buschsieweke@ovgu.de>

import sys
from testrunner import run


def testfunc(child):
child.expect("TEST PASSED")


if __name__ == "__main__":
sys.exit(run(testfunc))
80 changes: 0 additions & 80 deletions tests/pbkdf2/tests/02-random.py

This file was deleted.

Loading

0 comments on commit 2f6dd98

Please sign in to comment.