From c24243a2bc39c12f19670b3c9f02ca479e619d24 Mon Sep 17 00:00:00 2001 From: Michael R Sweet Date: Tue, 12 Oct 2021 09:13:30 -0400 Subject: [PATCH] Refactor crypto callback to have separate input/output pointers. Add initial writing support. --- pdfio-aes.c | 50 +++++++++++++++++++++++++----------- pdfio-file.c | 4 +-- pdfio-private.h | 9 +++---- pdfio-rc4.c | 7 ++++-- pdfio-stream.c | 67 ++++++++++++++++++++++++++++++++++++++++++++++--- pdfio.h | 2 +- testpdfio.c | 40 ++++++++++++++++++++++++++++- 7 files changed, 151 insertions(+), 28 deletions(-) diff --git a/pdfio-aes.c b/pdfio-aes.c index 6bd8348..0cad146 100644 --- a/pdfio-aes.c +++ b/pdfio-aes.c @@ -170,23 +170,34 @@ _pdfioCryptoAESInit( // // '_pdfioCryptoAESDecrypt()' - Decrypt a block of bytes with AES. // +// "inbuffer" and "outbuffer" can point to the same memory. +// void _pdfioCryptoAESDecrypt( _pdfio_aes_t *ctx, // I - AES context - uint8_t *buffer, // I - Buffer + uint8_t *outbuffer, // I - Output buffer + const uint8_t *inbuffer, // I - Input buffer size_t len) // I - Number of bytes to decrypt { uint8_t next_iv[16]; // Next IV value + if (inbuffer != outbuffer) + { + // Not the most efficient, but we can optimize later - the sample AES code + // manipulates the data directly in memory and doesn't support separate + // input and output buffers... + memcpy(outbuffer, inbuffer, len); + } + while (len > 15) { - memcpy(next_iv, buffer, 16); - InvCipher((state_t *)buffer, ctx); - XorWithIv(buffer, ctx->iv); + memcpy(next_iv, outbuffer, 16); + InvCipher((state_t *)outbuffer, ctx); + XorWithIv(outbuffer, ctx->iv); memcpy(ctx->iv, next_iv, 16); - buffer += 16; + outbuffer += 16; len -= 16; } @@ -196,14 +207,14 @@ _pdfioCryptoAESDecrypt( uint8_t temp[16]; // Temporary buffer memset(temp, 16 - len, sizeof(temp)); - memcpy(temp, buffer, len); + memcpy(temp, outbuffer, len); memcpy(next_iv, temp, 16); InvCipher((state_t *)temp, ctx); XorWithIv(temp, ctx->iv); memcpy(ctx->iv, next_iv, 16); - memcpy(buffer, temp, len); + memcpy(outbuffer, temp, len); } } @@ -211,23 +222,34 @@ _pdfioCryptoAESDecrypt( // // '_pdfioCryptoAESEncrypt()' - Encrypt a block of bytes with AES. // +// "inbuffer" and "outbuffer" can point to the same memory. +// void _pdfioCryptoAESEncrypt( _pdfio_aes_t *ctx, // I - AES context - uint8_t *buffer, // I - Buffer + uint8_t *outbuffer, // I - Output buffer + const uint8_t *inbuffer, // I - Input buffer size_t len) // I - Number of bytes to decrypt { uint8_t *iv = ctx->iv; // Current IV for CBC uint8_t temp[16]; // Temporary buffer + if (inbuffer != outbuffer) + { + // Not the most efficient, but we can optimize later - the sample AES code + // manipulates the data directly in memory and doesn't support separate + // input and output buffers... + memcpy(outbuffer, inbuffer, len); + } + while (len > 15) { - XorWithIv(buffer, iv); - Cipher((state_t*)buffer, ctx); - iv = buffer; - buffer += 16; + XorWithIv(outbuffer, iv); + Cipher((state_t*)outbuffer, ctx); + iv = outbuffer; + outbuffer += 16; len -= 16; } @@ -235,13 +257,13 @@ _pdfioCryptoAESEncrypt( { // Pad the final buffer with (16 - len)... memset(temp, 16 - len, sizeof(temp)); - memcpy(temp, buffer, len); + memcpy(temp, outbuffer, len); XorWithIv(temp, iv); Cipher((state_t*)temp, ctx); iv = temp; - memcpy(buffer, temp, len); + memcpy(outbuffer, temp, len); } /* store Iv in ctx for next call */ diff --git a/pdfio-file.c b/pdfio-file.c index 9cc0f28..93f3551 100644 --- a/pdfio-file.c +++ b/pdfio-file.c @@ -1159,7 +1159,7 @@ pdfioFileSetPermissions( encrypt_key[j] = (uint8_t)(digest[j] ^ i); _pdfioCryptoRC4Init(&rc4, encrypt_key, sizeof(encrypt_key)); - _pdfioCryptoRC4Crypt(&rc4, pdf->owner_key, sizeof(pdf->owner_key)); + _pdfioCryptoRC4Crypt(&rc4, pdf->owner_key, pdf->owner_key, sizeof(pdf->owner_key)); } // Generate the encryption key @@ -1204,7 +1204,7 @@ pdfioFileSetPermissions( digest[j] = (uint8_t)(pdf->encryption_key[j] ^ i); _pdfioCryptoRC4Init(&rc4, digest, 16); - _pdfioCryptoRC4Crypt(&rc4, pdf->user_key, sizeof(pdf->user_key)); + _pdfioCryptoRC4Crypt(&rc4, pdf->user_key, pdf->user_key, sizeof(pdf->user_key)); } // Save everything in the dictionary... diff --git a/pdfio-private.h b/pdfio-private.h index de0582b..51eb199 100644 --- a/pdfio-private.h +++ b/pdfio-private.h @@ -175,7 +175,6 @@ typedef struct _pdfio_value_s // Value structure } value; // Value union } _pdfio_value_t; - typedef struct _pdfio_aes_s // AES encryption state { size_t round_size; // Size of round key @@ -212,7 +211,7 @@ typedef union _pdfio_crypto_ctx_u // Cryptographic contexts _pdfio_aes_t aes; // AES-128/256 context _pdfio_rc4_t rc4; // RC4-40/128 context } _pdfio_crypto_ctx_t; -typedef void (*_pdfio_crypto_cb_t)(_pdfio_crypto_ctx_t *ctx, uint8_t *buffer, size_t len); +typedef void (*_pdfio_crypto_cb_t)(_pdfio_crypto_ctx_t *ctx, uint8_t *outbuffer, const uint8_t *inbuffer, size_t len); struct _pdfio_array_s { @@ -343,8 +342,8 @@ extern pdfio_array_t *_pdfioArrayRead(pdfio_file_t *pdf, _pdfio_token_t *ts) _PD extern bool _pdfioArrayWrite(pdfio_array_t *a) _PDFIO_INTERNAL; extern void _pdfioCryptoAESInit(_pdfio_aes_t *ctx, const uint8_t *key, size_t keylen, const uint8_t *iv) _PDFIO_INTERNAL; -extern void _pdfioCryptoAESDecrypt(_pdfio_aes_t *ctx, uint8_t *buffer, size_t len) _PDFIO_INTERNAL; -extern void _pdfioCryptoAESEncrypt(_pdfio_aes_t *ctx, uint8_t *buffer, size_t len) _PDFIO_INTERNAL; +extern void _pdfioCryptoAESDecrypt(_pdfio_aes_t *ctx, uint8_t *outbuffer, const uint8_t *inbuffer, size_t len) _PDFIO_INTERNAL; +extern void _pdfioCryptoAESEncrypt(_pdfio_aes_t *ctx, uint8_t *outbuffer, const uint8_t *inbuffer, size_t len) _PDFIO_INTERNAL; extern void _pdfioCryptoMakeRandom(uint8_t *buffer, size_t bytes) _PDFIO_INTERNAL; extern _pdfio_crypto_cb_t _pdfioCryptoMakeReader(pdfio_file_t *pdf, pdfio_obj_t *obj, _pdfio_crypto_ctx_t *ctx, uint8_t *iv, size_t *ivlen) _PDFIO_INTERNAL; extern _pdfio_crypto_cb_t _pdfioCryptoMakeWriter(pdfio_file_t *pdf, pdfio_obj_t *obj, _pdfio_crypto_ctx_t *ctx, uint8_t *iv, size_t *ivlen) _PDFIO_INTERNAL; @@ -352,7 +351,7 @@ extern void _pdfioCryptoMD5Append(_pdfio_md5_t *pms, const uint8_t *data, size_ extern void _pdfioCryptoMD5Finish(_pdfio_md5_t *pms, uint8_t digest[16]) _PDFIO_INTERNAL; extern void _pdfioCryptoMD5Init(_pdfio_md5_t *pms) _PDFIO_INTERNAL; extern void _pdfioCryptoRC4Init(_pdfio_rc4_t *ctx, const uint8_t *key, size_t keylen) _PDFIO_INTERNAL; -extern void _pdfioCryptoRC4Crypt(_pdfio_rc4_t *ctx, uint8_t *buffer, size_t len) _PDFIO_INTERNAL; +extern void _pdfioCryptoRC4Crypt(_pdfio_rc4_t *ctx, uint8_t *outbuffer, const uint8_t *inbuffer, size_t len) _PDFIO_INTERNAL; extern void _pdfioCryptoSHA256Append(_pdfio_sha256_t *, const uint8_t *bytes, size_t bytecount) _PDFIO_INTERNAL; extern void _pdfioCryptoSHA256Init(_pdfio_sha256_t *ctx) _PDFIO_INTERNAL; extern void _pdfioCryptoSHA256Finish(_pdfio_sha256_t *ctx, uint8_t *Message_Digest) _PDFIO_INTERNAL; diff --git a/pdfio-rc4.c b/pdfio-rc4.c index 69d9a96..77cdaea 100644 --- a/pdfio-rc4.c +++ b/pdfio-rc4.c @@ -66,11 +66,14 @@ _pdfioCryptoRC4Init( // // '_pdfioCryptoRC4Crypt()' - De/encrypt the given buffer. // +// "inbuffer" and "outbuffer" can point to the same memory. +// void _pdfioCryptoRC4Crypt( _pdfio_rc4_t *ctx, // I - Context - uint8_t *buffer, // I - Buffer + uint8_t *outbuffer, // I - Output buffer + const uint8_t *inbuffer, // I - Input buffer size_t len) // I - Size of buffers { uint8_t tmp, // Swap variable @@ -97,7 +100,7 @@ _pdfioCryptoRC4Crypt( t = ctx->sbox[i] + ctx->sbox[j]; // Encrypt using the S box... - *buffer++ ^= ctx->sbox[t]; + *outbuffer++ = *inbuffer++ ^ ctx->sbox[t]; len --; } diff --git a/pdfio-stream.c b/pdfio-stream.c index bd6f832..8cb792f 100644 --- a/pdfio-stream.c +++ b/pdfio-stream.c @@ -61,6 +61,12 @@ pdfioStreamClose(pdfio_stream_t *st) // I - Stream goto done; } + if (st->crypto_cb) + { + // Encrypt it first... + (st->crypto_cb)(&st->crypto_ctx, st->cbuffer, st->cbuffer, sizeof(st->cbuffer) - st->flate.avail_out); + } + if (!_pdfioFileWrite(st->pdf, st->cbuffer, sizeof(st->cbuffer) - st->flate.avail_out)) { ret = false; @@ -74,6 +80,12 @@ pdfioStreamClose(pdfio_stream_t *st) // I - Stream if (st->flate.avail_out < (uInt)sizeof(st->cbuffer)) { // Write any residuals... + if (st->crypto_cb) + { + // Encrypt it first... + (st->crypto_cb)(&st->crypto_ctx, st->cbuffer, st->cbuffer, sizeof(st->cbuffer) - st->flate.avail_out); + } + if (!_pdfioFileWrite(st->pdf, st->cbuffer, sizeof(st->cbuffer) - st->flate.avail_out)) { ret = false; @@ -160,6 +172,22 @@ _pdfioStreamCreate( st->length_obj = length_obj; st->filter = compression; + if (obj->pdf->encryption) + { + uint8_t iv[64]; // Initialization vector + size_t ivlen = sizeof(iv); // Length of initialization vector, if any + + if ((st->crypto_cb = _pdfioCryptoMakeWriter(st->pdf, obj, &st->crypto_ctx, iv, &ivlen)) == NULL) + { + // TODO: Add error message? + free(st); + return (NULL); + } + + if (ivlen > 0) + _pdfioFileWrite(st->pdf, iv, ivlen); + } + if (compression == PDFIO_FILTER_FLATE) { // Flate compression @@ -592,7 +620,7 @@ pdfioStreamPrintf( // -// '()' - Write a single character to a stream. +// 'pdfioStreamPutChar()' - Write a single character to a stream. // bool // O - `true` on success, `false` on failure @@ -722,8 +750,35 @@ pdfioStreamWrite( // Write it... if (st->filter == PDFIO_FILTER_NONE) { - // No filtering so just write it... - return (_pdfioFileWrite(st->pdf, buffer, bytes)); + // No filtering... + if (st->crypto_cb) + { + // Encrypt data before writing... + unsigned char temp[8192]; // Temporary buffer + size_t cbytes; // Current bytes + + bufptr = (const unsigned char *)buffer; + + while (bytes > 0) + { + if ((cbytes = bytes) > sizeof(temp)) + cbytes = sizeof(temp); + + (st->crypto_cb)(&st->crypto_ctx, temp, bufptr, cbytes); + if (!_pdfioFileWrite(st->pdf, temp, cbytes)) + return (false); + + bytes -= cbytes; + bufptr += cbytes; + } + + return (true); + } + else + { + // Write unencrypted... + return (_pdfioFileWrite(st->pdf, buffer, bytes)); + } } pbline = st->pbsize - 1; @@ -1098,6 +1153,12 @@ stream_write(pdfio_stream_t *st, // I - Stream if (st->flate.avail_out < (sizeof(st->cbuffer) / 8)) { // Flush the compression buffer... + if (st->crypto_cb) + { + // Encrypt it first... + (st->crypto_cb)(&st->crypto_ctx, st->cbuffer, st->cbuffer, sizeof(st->cbuffer) - st->flate.avail_out); + } + if (!_pdfioFileWrite(st->pdf, st->cbuffer, sizeof(st->cbuffer) - st->flate.avail_out)) return (false); diff --git a/pdfio.h b/pdfio.h index 1bd4093..cf6397c 100644 --- a/pdfio.h +++ b/pdfio.h @@ -99,7 +99,7 @@ enum pdfio_permission_e // PDF permission bits PDFIO_PERMISSION_PRINT_HIGH = 0x0800, // PDF allows high quality printing PDFIO_PERMISSION_ALL = ~0 // All permissions }; -typedef unsigned pdfio_permission_t; // PDF permission bitfield +typedef int pdfio_permission_t; // PDF permission bitfield typedef struct pdfio_rect_s // PDF rectangle { double x1; // Lower-left X coordinate diff --git a/testpdfio.c b/testpdfio.c index 42bbc70..b841baa 100644 --- a/testpdfio.c +++ b/testpdfio.c @@ -763,7 +763,7 @@ do_unit_tests(void) if (read_unit_file("testpdfio-out.pdf", num_pages, first_image, false)) return (1); - // Create a new PDF file... + // Stream a new PDF file... if ((outfd = open("testpdfio-out2.pdf", O_CREAT | O_TRUNC | O_WRONLY | O_BINARY, 0666)) < 0) { perror("Unable to open \"testpdfio-out2.pdf\""); @@ -784,6 +784,44 @@ do_unit_tests(void) if (read_unit_file("testpdfio-out2.pdf", num_pages, first_image, true)) return (1); + // Create new encrypted PDF files... + fputs("pdfioFileCreate(\"testpdfio-rc4.pdf\", ...): ", stdout); + if ((outpdf = pdfioFileCreate("testpdfio-rc4.pdf", NULL, NULL, NULL, (pdfio_error_cb_t)error_cb, &error)) != NULL) + puts("PASS"); + else + return (1); + + fputs("pdfioFileSetPermissions(all, RC4-128, no passwords): ", stdout); + if (pdfioFileSetPermissions(outpdf, PDFIO_PERMISSION_ALL, PDFIO_ENCRYPTION_RC4_128, NULL, NULL)) + puts("PASS"); + else + return (1); + + if (write_unit_file(inpdf, outpdf, &num_pages, &first_image)) + return (1); + +// if (read_unit_file("testpdfio-rc4.pdf", num_pages, first_image, false)) +// return (1); + + fputs("pdfioFileCreate(\"testpdfio-aes.pdf\", ...): ", stdout); + if ((outpdf = pdfioFileCreate("testpdfio-aes.pdf", NULL, NULL, NULL, (pdfio_error_cb_t)error_cb, &error)) != NULL) + puts("PASS"); + else + return (1); + + fputs("pdfioFileSetPermissions(no-print, AES-128, passwords='owner' and 'user'): ", stdout); + if (pdfioFileSetPermissions(outpdf, PDFIO_PERMISSION_ALL ^ PDFIO_PERMISSION_PRINT, PDFIO_ENCRYPTION_AES_128, "owner", "user")) + puts("PASS"); + else + return (1); + + if (write_unit_file(inpdf, outpdf, &num_pages, &first_image)) + return (1); + +// if (read_unit_file("testpdfio-aes.pdf", num_pages, first_image, false)) +// return (1); + + return (0); }