@@ -302,29 +302,32 @@ static int decrypt_step(void *state, void *buffer, size_t size)
int ret;
int inlen;
- if (s->outlen != 0) {
- if ((int)size > s->outlen) {
- size = s->outlen;
+ for (;;) {
+ if (s->outlen != 0) {
+ if ((int)size > s->outlen) {
+ size = s->outlen;
+ }
+ memcpy(buffer, s->output, size);
+ s->outlen -= size;
+ memmove(s->output, s->output + size, s->outlen);
+ return size;
}
- memcpy(buffer, s->output, size);
- s->outlen -= size;
- memmove(s->output, s->output + size, s->outlen);
- return size;
- }
- ret = s->upstream_step(s->upstream_state, s->input, sizeof s->input);
- if (ret < 0) {
- return ret;
- }
+ if (s->eof) {
+ return 0;
+ }
- inlen = ret;
+ ret = s->upstream_step(s->upstream_state, s->input, sizeof s->input);
+ if (ret < 0) {
+ return ret;
+ }
+
+ inlen = ret;
- if (!s->eof) {
if (inlen != 0) {
ret = swupdate_DECRYPT_update(s->dcrypt,
s->output, &s->outlen, s->input, inlen);
- }
- if (inlen == 0) {
+ } else {
/*
* Finalise the decryption. Further plaintext bytes may
* be written at this stage.
@@ -339,18 +342,6 @@ static int decrypt_step(void *state, void *buffer, size_t size)
return ret;
}
}
-
- if (s->outlen != 0) {
- if ((int)size > s->outlen) {
- size = s->outlen;
- }
- memcpy(buffer, s->output, size);
- s->outlen -= size;
- memmove(s->output, s->output + size, s->outlen);
- return size;
- }
-
- return 0;
}
#if defined(CONFIG_GUNZIP) || defined(CONFIG_ZSTD) || defined(CONFIG_XZ) || defined(CONFIG_LZ4)
@@ -180,6 +180,10 @@ $(DATADIR)/softshm: $(DATADIR)/token/softhsm.conf PREPARE_DATA
$(Q)openssl enc -aes-256-cbc -in $(DATADIR)/token/original.data -out $(DATADIR)/token/encrypted.data -K $(TOKEN_AES_KEY) -iv $(TOKEN_AES_IV)
$(Q)echo -n "$(TOKEN_AES_IV)" | xxd -p -r > $(DATADIR)/token/encrypted.data.iv
+ $(if $(Q),@echo " GEN $(DATADIR)/token/small.data")
+ $(Q)printf "1.2.3\n" > $(DATADIR)/token/small.data
+ $(Q)openssl enc -aes-256-cbc -in $(DATADIR)/token/small.data -out $(DATADIR)/token/small.encrypted.data -K $(TOKEN_AES_KEY) -iv $(TOKEN_AES_IV)
+
$(if $(Q),@echo " IMPORT $(DATADIR)/token/aes.key")
$(Q)echo -n "$(TOKEN_AES_KEY)" | xxd -p -r > $(DATADIR)/token/aes.key
$(Q)softhsm2-util --init-token --slot 0 --label "TestToken" --so-pin 123456 --pin 1234
@@ -7,6 +7,9 @@
#include <stdarg.h>
#include <stddef.h>
#include <setjmp.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <sys/stat.h>
#include <cmocka.h>
#include <util.h>
#include "swupdate_crypto.h"
@@ -14,6 +17,8 @@
#define BUFFER_SIZE (AES_BLK_SIZE * 1024)
#define TOKENDIR "test/data/token"
+static unsigned char small_data[AES_BLK_SIZE];
+
static int read_file(const char *path, unsigned char *buffer, size_t *size)
{
FILE *fp = fopen(path, "r");
@@ -90,10 +95,69 @@ static void test_crypt_pkcs11_256(void **state)
assert_true(strncmp((const char *)decrypted_data, (const char *)original_data, original_data_len) == 0);
}
+static size_t captured_len = 0;
+
+static int capture_output(void *out, const void *buf, size_t len)
+{
+ (void)out;
+ assert_true(captured_len + len <= sizeof(small_data));
+ memcpy(&small_data[captured_len], buf, len);
+ captured_len += len;
+ return 0;
+}
+
+/*
+ * Regression test: decrypting a file that fits into a single AES block
+ * must yield its plaintext. Files of 16 bytes or less (e.g. a version
+ * string) have their whole plaintext buffered inside the decrypt context
+ * until the final call, so the copy pipeline must not stop early.
+ */
+static void test_crypt_pkcs11_single_block(void **state)
+{
+ (void) state;
+ int err;
+
+ static const char *uri =
+ "pkcs11:token=TestToken;id=%A1%B2?pin-value=1234&module-path=/usr/lib/softhsm/libsofthsm2.so";
+
+ static const char version[] = "1.2.3\n";
+
+ err = set_aes_key(uri, "c1f390d21dd06118cbd333144a3318ca");
+ assert_true(err == 0);
+
+ struct stat sb;
+ err = stat(TOKENDIR "/small.encrypted.data", &sb);
+ assert_true(err == 0);
+ assert_true(sb.st_size == AES_BLK_SIZE);
+
+ unsigned long offs = 0;
+ int fdin = open(TOKENDIR "/small.encrypted.data", O_RDONLY);
+ assert_true(fdin >= 0);
+
+ struct swupdate_copy copy = {
+ .fdin = fdin,
+ .out = NULL,
+ .callback = capture_output,
+ .nbytes = sb.st_size,
+ .offs = &offs,
+ .encrypted = true,
+ .cipher = AES_CBC,
+ };
+
+ captured_len = 0;
+ err = copyfile(©);
+ close(fdin);
+ assert_true(err == 0);
+
+ assert_true(captured_len == sizeof(version) - 1);
+ assert_true(memcmp(small_data, version, captured_len) == 0);
+}
+
int main(void)
{
const struct CMUnitTest crypt_pkcs11_tests[] = {
- cmocka_unit_test(test_crypt_pkcs11_256)
+ cmocka_unit_test(test_crypt_pkcs11_256),
+ cmocka_unit_test(test_crypt_pkcs11_single_block)
};
return cmocka_run_group_tests_name("crypt_pkcs11", crypt_pkcs11_tests, NULL, NULL);
}
The p11-kit PKCS#11 decrypt backend holds back the last AES block internally and only emits it on the next DECRYPT_update call or on DECRYPT_final. For ciphertext fitting into a single block (e.g. a 6-byte version string), the only DECRYPT_update call yields no output and decrypt_step returned zero, which copyfile() treats as end-of-stream. DECRYPT_final was then never called and the file was written empty. Let decrypt_step() keep pulling input until decrypted output is available or the stream is fully finalized. Signed-off-by: Bastian Germann <bage@debian.org> --- core/cpio_utils.c | 47 ++++++++++++---------------- test/Makefile | 4 +++ test/test_crypt_pkcs11.c | 66 +++++++++++++++++++++++++++++++++++++++- 3 files changed, 88 insertions(+), 29 deletions(-)