From 234c3a73815ce4b23feb01a5d6e5401134661597 Mon Sep 17 00:00:00 2001 From: Michael R Sweet Date: Sun, 24 Oct 2021 10:59:25 -0400 Subject: [PATCH] Do some reorganization and start the implementation of decryption. --- FAQ.md | 17 --- pdfio-crypto.c | 356 ++++++++++++++++++++++++++++++++++++++++++++++++ pdfio-file.c | 302 +++++++++------------------------------- pdfio-private.h | 5 + pdfio-stream.c | 39 +++++- pdfio.h | 2 +- 6 files changed, 465 insertions(+), 256 deletions(-) delete mode 100644 FAQ.md diff --git a/FAQ.md b/FAQ.md deleted file mode 100644 index f595386..0000000 --- a/FAQ.md +++ /dev/null @@ -1,17 +0,0 @@ -Frequently Asked Questions -========================== - -Why Don't You Support Writing a PDF File with Encryption? ---------------------------------------------------------- - -PDF encryption offers very little protection: - -- PDF encryption keys are reused and derived from the user password (padded - with a standard base string) and the object numbers in the file. -- RC4 encryption (40- and 128-bit) was broken years ago. -- AES encryption (128- and 256-bit) is better, but PDF uses Cipher Block - Chaining (CBC) which enables attacks that allow the original encryption key - to be recovered. - -In addition, PDF usage controls (no print, no copy, etc.) are tied to this -encryption, making them trivial to bypass. diff --git a/pdfio-crypto.c b/pdfio-crypto.c index 2a55aac..347063c 100644 --- a/pdfio-crypto.c +++ b/pdfio-crypto.c @@ -23,6 +23,228 @@ #endif // __has_include +// +// Local globals... +// + +static uint8_t pdf_passpad[32] = // Padding for passwords +{ + 0x28, 0xbf, 0x4e, 0x5e, 0x4e, 0x75, 0x8a, 0x41, + 0x64, 0x00, 0x4e, 0x56, 0xff, 0xfa, 0x01, 0x08, + 0x2e, 0x2e, 0x00, 0xb6, 0xd0, 0x68, 0x3e, 0x80, + 0x2f, 0x0c, 0xa9, 0xfe, 0x64, 0x53, 0x69, 0x7a +}; + + +// +// '_pdfioCryptoLock()' - Lock a PDF file by generating the encryption object and keys. +// + +bool // O - `true` on success, `false` otherwise +_pdfioCryptoLock( + pdfio_file_t *pdf, // I - PDF file + pdfio_permission_t permissions, // I - Use permissions + pdfio_encryption_t encryption, // I - Type of encryption to use + const char *owner_password, // I - Owner password, if any + const char *user_password) // I - User password, if any +{ + pdfio_dict_t *dict; // Encryption dictionary + size_t i, j; // Looping vars + _pdfio_md5_t md5; // MD5 context + uint8_t digest[16]; // 128-bit MD5 digest + _pdfio_rc4_t rc4; // RC4 encryption context + size_t len; // Length of password + uint8_t owner_pad[32], // Padded owner password + user_pad[32], // Padded user password + perm_bytes[4], // Permissions bytes + *file_id; // File ID bytes + size_t file_id_len; // Length of file ID + pdfio_dict_t *cf_dict, // CF dictionary + *filter_dict; // CryptFilter dictionary + + + if ((dict = pdfioDictCreate(pdf)) == NULL) + { + _pdfioFileError(pdf, "Unable to create encryption dictionary."); + return (false); + } + + pdfioDictSetName(dict, "Filter", "Standard"); + + switch (encryption) + { + case PDFIO_ENCRYPTION_RC4_128 : + case PDFIO_ENCRYPTION_AES_128 : + // Create the 128-bit encryption keys... + if (user_password) + { + // Use the specified user password + if ((len = strlen(user_password)) > sizeof(user_pad)) + len = sizeof(user_pad); + } + else + { + // No user password + len = 0; + } + + if (len > 0) + memcpy(user_pad, user_password, len); + if (len < sizeof(user_pad)) + memcpy(user_pad + len, pdf_passpad, sizeof(user_pad) - len); + + if (owner_password) + { + // Use the specified owner password... + if ((len = strlen(owner_password)) > sizeof(owner_pad)) + len = sizeof(owner_pad); + } + else if (user_password && *user_password) + { + // Generate a random owner password... + _pdfioCryptoMakeRandom(owner_pad, sizeof(owner_pad)); + len = sizeof(owner_pad); + } + else + { + // No owner password + len = 0; + } + + if (len > 0) + memcpy(owner_pad, owner_password, len); + if (len < sizeof(owner_pad)) + memcpy(owner_pad + len, pdf_passpad, sizeof(owner_pad) - len); + + // Compute the owner key... + _pdfioCryptoMD5Init(&md5); + _pdfioCryptoMD5Append(&md5, owner_pad, 32); + _pdfioCryptoMD5Finish(&md5, digest); + + for (i = 0; i < 50; i ++) + { + _pdfioCryptoMD5Init(&md5); + _pdfioCryptoMD5Append(&md5, digest, 16); + _pdfioCryptoMD5Finish(&md5, digest); + } + + // Copy and encrypt the padded user password... + memcpy(pdf->owner_key, user_pad, sizeof(pdf->owner_key)); + + for (i = 0; i < 20; i ++) + { + uint8_t encrypt_key[16]; // RC4 encryption key + + // XOR each byte in the digest with the loop counter to make a key... + for (j = 0; j < sizeof(encrypt_key); j ++) + encrypt_key[j] = (uint8_t)(digest[j] ^ i); + + _pdfioCryptoRC4Init(&rc4, encrypt_key, sizeof(encrypt_key)); + _pdfioCryptoRC4Crypt(&rc4, pdf->owner_key, pdf->owner_key, sizeof(pdf->owner_key)); + } + pdf->owner_keylen = 32; + + // Generate the encryption key + perm_bytes[0] = (uint8_t)permissions; + perm_bytes[1] = (uint8_t)(permissions >> 8); + perm_bytes[2] = (uint8_t)(permissions >> 16); + perm_bytes[3] = (uint8_t)(permissions >> 24); + + file_id = pdfioArrayGetBinary(pdf->id_array, 0, &file_id_len); + + _pdfioCryptoMD5Init(&md5); + _pdfioCryptoMD5Append(&md5, user_pad, 32); + _pdfioCryptoMD5Append(&md5, pdf->owner_key, 32); + _pdfioCryptoMD5Append(&md5, perm_bytes, 4); + _pdfioCryptoMD5Append(&md5, file_id, file_id_len); + _pdfioCryptoMD5Finish(&md5, digest); + + // MD5 the result 50 times.. + for (i = 0; i < 50; i ++) + { + _pdfioCryptoMD5Init(&md5); + _pdfioCryptoMD5Append(&md5, digest, 16); + _pdfioCryptoMD5Finish(&md5, digest); + } + + memcpy(pdf->encryption_key, digest, 16); + pdf->encryption_keylen = 16; + + // Generate the user key... + _pdfioCryptoMD5Init(&md5); + _pdfioCryptoMD5Append(&md5, pdf_passpad, 32); + _pdfioCryptoMD5Append(&md5, file_id, file_id_len); + _pdfioCryptoMD5Finish(&md5, pdf->user_key); + + memset(pdf->user_key + 16, 0, 16); + + // Encrypt the result 20 times... + for (i = 0; i < 20; i ++) + { + // XOR each byte in the key with the loop counter... + for (j = 0; j < 16; j ++) + digest[j] = (uint8_t)(pdf->encryption_key[j] ^ i); + + _pdfioCryptoRC4Init(&rc4, digest, 16); + _pdfioCryptoRC4Crypt(&rc4, pdf->user_key, pdf->user_key, sizeof(pdf->user_key)); + } + pdf->user_keylen = 32; + + // Save everything in the dictionary... + pdfioDictSetNumber(dict, "Length", 128); + pdfioDictSetBinary(dict, "O", pdf->owner_key, sizeof(pdf->owner_key)); + pdfioDictSetNumber(dict, "P", (int)permissions); + pdfioDictSetNumber(dict, "R", encryption == PDFIO_ENCRYPTION_RC4_128 ? 3 : 4); + pdfioDictSetNumber(dict, "V", encryption == PDFIO_ENCRYPTION_RC4_128 ? 2 : 4); + pdfioDictSetBinary(dict, "U", pdf->user_key, sizeof(pdf->user_key)); + + if (encryption == PDFIO_ENCRYPTION_AES_128) + { + if ((cf_dict = pdfioDictCreate(pdf)) == NULL) + { + _pdfioFileError(pdf, "Unable to create Encryption CF dictionary."); + return (false); + } + + if ((filter_dict = pdfioDictCreate(pdf)) == NULL) + { + _pdfioFileError(pdf, "Unable to create Encryption CryptFilter dictionary."); + return (false); + } + + pdfioDictSetName(filter_dict, "Type", "CryptFilter"); + pdfioDictSetName(filter_dict, "CFM", encryption == PDFIO_ENCRYPTION_RC4_128 ? "V2" : "AESV2"); + pdfioDictSetDict(cf_dict, "PDFio", filter_dict); + pdfioDictSetDict(dict, "CF", cf_dict); + pdfioDictSetName(dict, "StmF", "PDFio"); + pdfioDictSetName(dict, "StrF", "PDFio"); + pdfioDictSetBoolean(dict, "EncryptMetadata", true); + } + break; + + case PDFIO_ENCRYPTION_AES_256 : + // TODO: Implement AES-256 (/V 6 /R 6) + + default : + _pdfioFileError(pdf, "Encryption mode %d not supported for writing.", (int)encryption); + return (false); + } + + if ((pdf->encrypt_obj = pdfioFileCreateObj(pdf, dict)) == NULL) + { + _pdfioFileError(pdf, "Unable to create encryption object."); + return (false); + } + + pdfioObjClose(pdf->encrypt_obj); + + pdf->encryption = encryption; + pdf->permissions = permissions; + + return (true); +} + + // // '_pdfioCryptoMakeRandom()' - Fill a buffer with good random numbers. // @@ -323,3 +545,137 @@ _pdfio_crypto_cb_t // O - Encryption callback or `NULL` for none } } } + + +// +// '_pdfioCryptoUnlock()' - Unlock an encrypted PDF. +// + +bool // O - `true` on success, `false` otherwise +_pdfioCryptoUnlock( + pdfio_file_t *pdf, // I - PDF file + pdfio_password_cb_t password_cb, // I - Password callback or `NULL` for none + void *password_data) // I - Password callback data, if any +{ + int tries; // Number of tries + const char *password = NULL; // Password to try + pdfio_dict_t *encrypt_dict; // Encrypt objection dictionary + int version, // Version value + revision, // Revision value + length; // Key length value + const char *handler, // Security handler name + *stream_filter, // Stream encryption filter + *string_filter; // String encryption filter + pdfio_dict_t *cf_dict; // CryptFilters dictionary + unsigned char *owner_key, // Owner key + *user_key, // User key + *file_id; // File ID value + size_t owner_keylen, // Length of owner key + user_keylen, // Length of user key + file_idlen; // Length of file ID + + + // See if we support the type of encryption specified by the Encrypt object + // dictionary... + if ((encrypt_dict = pdfioObjGetDict(pdf->encrypt_obj)) == NULL) + { + _pdfioFileError(pdf, "Unable to get encryption dictionary."); + return (false); + } + + handler = pdfioDictGetName(encrypt_dict, "Filter"); + version = pdfioDictGetNumber(encrypt_dict, "V"); + revision = pdfioDictGetNumber(encrypt_dict, "R"); + length = pdfioDictGetNumber(encrypt_dict, "Length"); + stream_filter = pdfioDictGetName(encrypt_dict, "StmF"); + string_filter = pdfioDictGetName(encrypt_dict, "StrF"); + cf_dict = pdfioDictGetDict(encrypt_dict, "CF"); + + if (!handler || strcmp(handler, "Standard")) + { + _pdfioFileError(pdf, "Unsupported security handler '%s'.", handler ? handler : "(null)"); + return (false); + } + + if (version == 4 && revision == 4) + { + // Lookup crypt filter to see if we support it... + pdfio_dict_t *filter; // Crypt Filter + const char *cfm; // Crypt filter method + + if ((filter = pdfioDictGetDict(cf_dict, stream_filter)) != NULL && (cfm = pdfioDictGetName(filter, "CFM")) != NULL) + { + if (!strcmp(cfm, "V2")) + { + pdf->encryption = PDFIO_ENCRYPTION_RC4_128; + if (length < 40 || length > 128) + length = 128; + } + if (!strcmp(cfm, "AESV2")) + { + pdf->encryption = PDFIO_ENCRYPTION_AES_128; + length = 128; + } + } + } + else if (version == 2) + { + if (revision == 2) + { + pdf->encryption = PDFIO_ENCRYPTION_RC4_40; + length = 40; + } + else if (revision == 3) + { + pdf->encryption = PDFIO_ENCRYPTION_RC4_128; + if (length < 40 || length > 128) + length = 128; + } + } + // TODO: Implement AES-256 - V6 R6 + + if (pdf->encryption == PDFIO_ENCRYPTION_NONE) + { + _pdfioFileError(pdf, "Unsupported encryption V%d R%d.", version, revision); + return (false); + } + + // Grab the remaining values we need to unlock the PDF... + pdf->encryption_keylen = length / 8; + pdf->permissions = pdfioDictGetNumber(encrypt_dict, "P"); + + owner_key = pdfioDictGetBinary(encrypt_dict, "O", &owner_keylen); + user_key = pdfioDictGetBinary(encrypt_dict, "U", &user_keylen); + + if (!owner_key || owner_keylen < 32 || owner_keylen > sizeof(pdf->owner_key)) + { + _pdfioFileError(pdf, "Missing or bad owner key, unable to unlock file."); + return (false); + } + + memcpy(pdf->owner_key, owner_key, owner_keylen); + pdf->owner_keylen = owner_keylen; + + if (!user_key || user_keylen < 32 || user_keylen > sizeof(pdf->user_key)) + { + _pdfioFileError(pdf, "Missing or bad user key, unable to unlock file."); + return (false); + } + + memcpy(pdf->user_key, user_key, user_keylen); + pdf->user_keylen = user_keylen; + + if ((file_id = pdfioArrayGetBinary(pdf->id_array, 0, &file_idlen)) == NULL || file_idlen < 16) + { + _pdfioFileError(pdf, "Missing or bad file ID, unable to unlock file."); + return (false); + } + + // Now try to unlock the PDF... + for (tries = 0; tries < 4; tries ++) + { + // + } + + return (false); +} diff --git a/pdfio-file.c b/pdfio-file.c index b2885dd..5453cfa 100644 --- a/pdfio-file.c +++ b/pdfio-file.c @@ -26,7 +26,7 @@ static int compare_objmaps(_pdfio_objmap_t *a, _pdfio_objmap_t *b); static int compare_objs(pdfio_obj_t **a, pdfio_obj_t **b); static bool load_obj_stream(pdfio_obj_t *obj); static bool load_pages(pdfio_file_t *pdf, pdfio_obj_t *obj); -static bool load_xref(pdfio_file_t *pdf, off_t xref_offset); +static bool load_xref(pdfio_file_t *pdf, off_t xref_offset, pdfio_password_cb_t password_cb, void *password_data); static bool write_catalog(pdfio_file_t *pdf); static bool write_pages(pdfio_file_t *pdf); static bool write_trailer(pdfio_file_t *pdf); @@ -221,13 +221,14 @@ pdfioFileCreate( return (NULL); } - pdf->filename = strdup(filename); - pdf->version = strdup(version); - pdf->mode = _PDFIO_MODE_WRITE; - pdf->error_cb = error_cb; - pdf->error_data = error_data; - pdf->bufptr = pdf->buffer; - pdf->bufend = pdf->buffer + sizeof(pdf->buffer); + pdf->filename = strdup(filename); + pdf->version = strdup(version); + pdf->mode = _PDFIO_MODE_WRITE; + pdf->error_cb = error_cb; + pdf->error_data = error_data; + pdf->permissions = PDFIO_PERMISSION_ALL; + pdf->bufptr = pdf->buffer; + pdf->bufend = pdf->buffer + sizeof(pdf->buffer); if (media_box) { @@ -496,13 +497,14 @@ pdfioFileCreateOutput( return (NULL); } - pdf->filename = strdup("output.pdf"); - pdf->version = strdup(version); - pdf->mode = _PDFIO_MODE_WRITE; - pdf->error_cb = error_cb; - pdf->error_data = error_data; - pdf->bufptr = pdf->buffer; - pdf->bufend = pdf->buffer + sizeof(pdf->buffer); + pdf->filename = strdup("output.pdf"); + pdf->version = strdup(version); + pdf->mode = _PDFIO_MODE_WRITE; + pdf->error_cb = error_cb; + pdf->error_data = error_data; + pdf->permissions = PDFIO_PERMISSION_ALL; + pdf->bufptr = pdf->buffer; + pdf->bufend = pdf->buffer + sizeof(pdf->buffer); if (media_box) { @@ -834,6 +836,35 @@ pdfioFileGetPage(pdfio_file_t *pdf, // I - PDF file } +// +// 'pdfioFileGetPermissions()' - Get the access permissions of a PDF file. +// +// This function returns the access permissions of a PDF file and (optionally) +// the type of encryption that has been used. +// + +pdfio_permission_t // O - Permission bits +pdfioFileGetPermissions( + pdfio_file_t *pdf, // I - PDF file + pdfio_encryption_t *encryption) // O - Type of encryption used or `NULL` to ignore +{ + // Range check input... + if (!pdf) + { + if (encryption) + *encryption = PDFIO_ENCRYPTION_NONE; + + return (PDFIO_PERMISSION_ALL); + } + + // Return values... + if (encryption) + *encryption = pdf->encryption; + + return (pdf->permissions); +} + + // // 'pdfioFileGetProducer()' - Get the producer string for a PDF file. // @@ -910,9 +941,6 @@ pdfioFileOpen( off_t xref_offset; // Offset to xref table - (void)password_cb; - (void)password_data; - // Range check input... if (!filename) return (NULL); @@ -935,10 +963,11 @@ pdfioFileOpen( return (NULL); } - pdf->filename = strdup(filename); - pdf->mode = _PDFIO_MODE_READ; - pdf->error_cb = error_cb; - pdf->error_data = error_data; + pdf->filename = strdup(filename); + pdf->mode = _PDFIO_MODE_READ; + pdf->error_cb = error_cb; + pdf->error_data = error_data; + pdf->permissions = PDFIO_PERMISSION_ALL; // Open the file... if ((pdf->fd = open(filename, O_RDONLY | O_BINARY)) < 0) @@ -985,7 +1014,7 @@ pdfioFileOpen( xref_offset = (off_t)strtol(ptr + 9, NULL, 10); - if (!load_xref(pdf, xref_offset)) + if (!load_xref(pdf, xref_offset, password_cb, password_data)) goto error; return (pdf); @@ -1074,28 +1103,6 @@ pdfioFileSetPermissions( const char *owner_password, // I - Owner password, if any const char *user_password) // I - User password, if any { - pdfio_dict_t *dict; // Encryption dictionary - size_t i, j; // Looping vars - _pdfio_md5_t md5; // MD5 context - uint8_t digest[16]; // 128-bit MD5 digest - _pdfio_rc4_t rc4; // RC4 encryption context - size_t len; // Length of password - uint8_t owner_pad[32], // Padded owner password - user_pad[32], // Padded user password - perm_bytes[4], // Permissions bytes - *file_id; // File ID bytes - size_t file_id_len; // Length of file ID - pdfio_dict_t *cf_dict, // CF dictionary - *filter_dict; // CryptFilter dictionary - static uint8_t pad[32] = // Padding for passwords - { - 0x28, 0xbf, 0x4e, 0x5e, 0x4e, 0x75, 0x8a, 0x41, - 0x64, 0x00, 0x4e, 0x56, 0xff, 0xfa, 0x01, 0x08, - 0x2e, 0x2e, 0x00, 0xb6, 0xd0, 0x68, 0x3e, 0x80, - 0x2f, 0x0c, 0xa9, 0xfe, 0x64, 0x53, 0x69, 0x7a - }; - - if (!pdf) return (false); @@ -1108,182 +1115,7 @@ pdfioFileSetPermissions( if (encryption == PDFIO_ENCRYPTION_NONE) return (true); - if ((dict = pdfioDictCreate(pdf)) == NULL) - { - _pdfioFileError(pdf, "Unable to create encryption dictionary."); - return (false); - } - - pdfioDictSetName(dict, "Filter", "Standard"); - - switch (encryption) - { - case PDFIO_ENCRYPTION_RC4_128 : - case PDFIO_ENCRYPTION_AES_128 : - // Create the 128-bit encryption keys... - if (user_password) - { - // Use the specified user password - if ((len = strlen(user_password)) > sizeof(user_pad)) - len = sizeof(user_pad); - } - else - { - // No user password - len = 0; - } - - if (len > 0) - memcpy(user_pad, user_password, len); - if (len < sizeof(user_pad)) - memcpy(user_pad + len, pad, sizeof(user_pad) - len); - - if (owner_password) - { - // Use the specified owner password... - if ((len = strlen(owner_password)) > sizeof(owner_pad)) - len = sizeof(owner_pad); - } - else if (user_password && *user_password) - { - // Generate a random owner password... - _pdfioCryptoMakeRandom(owner_pad, sizeof(owner_pad)); - len = sizeof(owner_pad); - } - else - { - // No owner password - len = 0; - } - - if (len > 0) - memcpy(owner_pad, owner_password, len); - if (len < sizeof(owner_pad)) - memcpy(owner_pad + len, pad, sizeof(owner_pad) - len); - - // Compute the owner key... - _pdfioCryptoMD5Init(&md5); - _pdfioCryptoMD5Append(&md5, owner_pad, 32); - _pdfioCryptoMD5Finish(&md5, digest); - - for (i = 0; i < 50; i ++) - { - _pdfioCryptoMD5Init(&md5); - _pdfioCryptoMD5Append(&md5, digest, 16); - _pdfioCryptoMD5Finish(&md5, digest); - } - - // Copy and encrypt the padded user password... - memcpy(pdf->owner_key, user_pad, sizeof(pdf->owner_key)); - - for (i = 0; i < 20; i ++) - { - uint8_t encrypt_key[16]; // RC4 encryption key - - // XOR each byte in the digest with the loop counter to make a key... - for (j = 0; j < sizeof(encrypt_key); j ++) - encrypt_key[j] = (uint8_t)(digest[j] ^ i); - - _pdfioCryptoRC4Init(&rc4, encrypt_key, sizeof(encrypt_key)); - _pdfioCryptoRC4Crypt(&rc4, pdf->owner_key, pdf->owner_key, sizeof(pdf->owner_key)); - } - - // Generate the encryption key - perm_bytes[0] = (uint8_t)permissions; - perm_bytes[1] = (uint8_t)(permissions >> 8); - perm_bytes[2] = (uint8_t)(permissions >> 16); - perm_bytes[3] = (uint8_t)(permissions >> 24); - - file_id = pdfioArrayGetBinary(pdf->id_array, 0, &file_id_len); - - _pdfioCryptoMD5Init(&md5); - _pdfioCryptoMD5Append(&md5, user_pad, 32); - _pdfioCryptoMD5Append(&md5, pdf->owner_key, 32); - _pdfioCryptoMD5Append(&md5, perm_bytes, 4); - _pdfioCryptoMD5Append(&md5, file_id, file_id_len); - _pdfioCryptoMD5Finish(&md5, digest); - - // MD5 the result 50 times.. - for (i = 0; i < 50; i ++) - { - _pdfioCryptoMD5Init(&md5); - _pdfioCryptoMD5Append(&md5, digest, 16); - _pdfioCryptoMD5Finish(&md5, digest); - } - - memcpy(pdf->encryption_key, digest, 16); - - // Generate the user key... - _pdfioCryptoMD5Init(&md5); - _pdfioCryptoMD5Append(&md5, pad, 32); - _pdfioCryptoMD5Append(&md5, file_id, file_id_len); - _pdfioCryptoMD5Finish(&md5, pdf->user_key); - - memset(pdf->user_key + 16, 0, 16); - - // Encrypt the result 20 times... - for (i = 0; i < 20; i ++) - { - // XOR each byte in the key with the loop counter... - for (j = 0; j < 16; j ++) - digest[j] = (uint8_t)(pdf->encryption_key[j] ^ i); - - _pdfioCryptoRC4Init(&rc4, digest, 16); - _pdfioCryptoRC4Crypt(&rc4, pdf->user_key, pdf->user_key, sizeof(pdf->user_key)); - } - - // Save everything in the dictionary... - pdfioDictSetNumber(dict, "Length", 128); - pdfioDictSetBinary(dict, "O", pdf->owner_key, sizeof(pdf->owner_key)); - pdfioDictSetNumber(dict, "P", (int)permissions); - pdfioDictSetNumber(dict, "R", encryption == PDFIO_ENCRYPTION_RC4_128 ? 3 : 4); - pdfioDictSetNumber(dict, "V", encryption == PDFIO_ENCRYPTION_RC4_128 ? 2 : 4); - pdfioDictSetBinary(dict, "U", pdf->user_key, sizeof(pdf->user_key)); - - if (encryption == PDFIO_ENCRYPTION_AES_128) - { - if ((cf_dict = pdfioDictCreate(pdf)) == NULL) - { - _pdfioFileError(pdf, "Unable to create Encryption CF dictionary."); - return (false); - } - - if ((filter_dict = pdfioDictCreate(pdf)) == NULL) - { - _pdfioFileError(pdf, "Unable to create Encryption CryptFilter dictionary."); - return (false); - } - - pdfioDictSetName(filter_dict, "Type", "CryptFilter"); - pdfioDictSetName(filter_dict, "CFM", encryption == PDFIO_ENCRYPTION_RC4_128 ? "V2" : "AESV2"); - pdfioDictSetDict(cf_dict, "PDFio", filter_dict); - pdfioDictSetDict(dict, "CF", cf_dict); - pdfioDictSetName(dict, "StmF", "PDFio"); - pdfioDictSetName(dict, "StrF", "PDFio"); - pdfioDictSetBoolean(dict, "EncryptMetadata", true); - } - break; - - case PDFIO_ENCRYPTION_AES_256 : - // TODO: Implement AES-256 (/V 6 /R 6) - - default : - _pdfioFileError(pdf, "Encryption mode %d not supported for writing.", (int)encryption); - return (false); - } - - if ((pdf->encrypt_obj = pdfioFileCreateObj(pdf, dict)) == NULL) - { - _pdfioFileError(pdf, "Unable to create encryption object."); - return (false); - } - - pdfioObjClose(pdf->encrypt_obj); - - pdf->encryption = encryption; - pdf->permissions = permissions; - - return (true); + return (_pdfioCryptoLock(pdf, permissions, encryption, owner_password, user_password)); } @@ -1567,8 +1399,11 @@ load_pages(pdfio_file_t *pdf, // I - PDF file // static bool // O - `true` on success, `false` on failure -load_xref(pdfio_file_t *pdf, // I - PDF file - off_t xref_offset) // I - Offset to xref +load_xref( + pdfio_file_t *pdf, // I - PDF file + off_t xref_offset, // I - Offset to xref + pdfio_password_cb_t password_cb, // I - Password callback or `NULL` for none + void *password_data) // I - Password callback data, if any { bool done = false; // Are we done? char line[1024], // Line from file @@ -1668,13 +1503,6 @@ load_xref(pdfio_file_t *pdf, // I - PDF file _pdfioFileError(pdf, "Cross-reference stream does not have a dictionary."); return (false); } - else if (_pdfioDictGetValue(pdf->trailer_dict, "Encrypt")) - { - // TODO: Fix me - // Encryption not yet supported... - _pdfioFileError(pdf, "Sorry, PDFio currently does not support encrypted PDF files."); - return (false); - } obj->value = trailer; @@ -1809,6 +1637,10 @@ load_xref(pdfio_file_t *pdf, // I - PDF file pdfioStreamClose(st); + // If the trailer contains an Encrypt key, try unlocking the file... + if ((pdf->encrypt_obj = pdfioDictGetObj(pdf->trailer_dict, "Encrypt")) != NULL && !_pdfioCryptoUnlock(pdf, password_cb, password_data)) + return (false); + // Load any object streams that are left... PDFIO_DEBUG("load_xref: %lu compressed object streams to load.\n", (unsigned long)num_sobjs); @@ -1916,12 +1748,6 @@ load_xref(pdfio_file_t *pdf, // I - PDF file _pdfioFileError(pdf, "Trailer is not a dictionary."); return (false); } - else if (_pdfioDictGetValue(pdf->trailer_dict, "Encrypt")) - { - // Encryption not yet supported... - _pdfioFileError(pdf, "Sorry, PDFio currently does not support encrypted PDF files."); - return (false); - } _pdfioTokenFlush(&tb); } @@ -1941,6 +1767,10 @@ load_xref(pdfio_file_t *pdf, // I - PDF file // Save the trailer dictionary and grab the root (catalog) and info // objects... pdf->trailer_dict = trailer.value.dict; + + // If the trailer contains an Encrypt key, try unlocking the file... + if ((pdf->encrypt_obj = pdfioDictGetObj(pdf->trailer_dict, "Encrypt")) != NULL && !_pdfioCryptoUnlock(pdf, password_cb, password_data)) + return (false); } if ((xref_offset = (off_t)pdfioDictGetNumber(trailer.value.dict, "Prev")) <= 0) diff --git a/pdfio-private.h b/pdfio-private.h index dd3975b..ef79056 100644 --- a/pdfio-private.h +++ b/pdfio-private.h @@ -259,6 +259,9 @@ struct _pdfio_file_s // PDF file structure uint8_t encryption_key[16], // Object encryption key owner_key[32], // Owner encryption key user_key[32]; // User encryption key + size_t encryption_keylen, // Length of encryption key + owner_keylen, // Length of owner encryption key + user_keylen; // Length of user encryption key // Active file data int fd; // File descriptor @@ -344,6 +347,7 @@ extern bool _pdfioArrayWrite(pdfio_array_t *a, pdfio_obj_t *obj) _PDFIO_INTERNA extern void _pdfioCryptoAESInit(_pdfio_aes_t *ctx, const uint8_t *key, size_t keylen, const uint8_t *iv) _PDFIO_INTERNAL; extern size_t _pdfioCryptoAESDecrypt(_pdfio_aes_t *ctx, uint8_t *outbuffer, const uint8_t *inbuffer, size_t len) _PDFIO_INTERNAL; extern size_t _pdfioCryptoAESEncrypt(_pdfio_aes_t *ctx, uint8_t *outbuffer, const uint8_t *inbuffer, size_t len) _PDFIO_INTERNAL; +extern bool _pdfioCryptoLock(pdfio_file_t *pdf, pdfio_permission_t permissions, pdfio_encryption_t encryption, const char *owner_password, const char *user_password) _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; @@ -355,6 +359,7 @@ extern size_t _pdfioCryptoRC4Crypt(_pdfio_rc4_t *ctx, uint8_t *outbuffer, const 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; +extern bool _pdfioCryptoUnlock(pdfio_file_t *pdf, pdfio_password_cb_t password_cb, void *password_data) _PDFIO_INTERNAL; extern void _pdfioDictDebug(pdfio_dict_t *dict, FILE *fp) _PDFIO_INTERNAL; extern void _pdfioDictDelete(pdfio_dict_t *dict) _PDFIO_INTERNAL; diff --git a/pdfio-stream.c b/pdfio-stream.c index 135fee1..fcd07af 100644 --- a/pdfio-stream.c +++ b/pdfio-stream.c @@ -121,7 +121,7 @@ pdfioStreamClose(pdfio_stream_t *st) // I - Stream uint8_t temp[8192]; // Temporary buffer size_t outbytes; // Output bytes - outbytes = (st->crypto_cb)(&st->crypto_ctx, temp, st->buffer, (size_t)(st->bufptr - st->buffer)); + outbytes = (st->crypto_cb)(&st->crypto_ctx, temp, (uint8_t *)st->buffer, (size_t)(st->bufptr - st->buffer)); if (!_pdfioFileWrite(st->pdf, temp, outbytes)) { ret = false; @@ -423,6 +423,24 @@ _pdfioStreamOpen(pdfio_obj_t *obj, // I - Object return (NULL); } + if (obj->pdf->encryption) + { + uint8_t iv[64]; // Initialization vector + size_t ivlen; // Length of initialization vector, if any + + ivlen = _pdfioFilePeek(st->pdf, iv, sizeof(iv)); + + if ((st->crypto_cb = _pdfioCryptoMakeReader(st->pdf, obj, &st->crypto_ctx, iv, &ivlen)) == NULL) + { + // TODO: Add error message? + free(st); + return (NULL); + } + + if (ivlen > 0) + _pdfioFileConsume(st->pdf, ivlen); + } + if (decode) { // Try to decode/decompress the contents of this object... @@ -537,6 +555,9 @@ _pdfioStreamOpen(pdfio_obj_t *obj, // I - Object return (NULL); } + if (st->crypto_cb) + rbytes = (st->crypto_cb)(&st->crypto_ctx, st->cbuffer, st->cbuffer, rbytes); + st->flate.next_in = (Bytef *)st->cbuffer; st->flate.avail_in = (uInt)rbytes; @@ -810,7 +831,7 @@ pdfioStreamWrite( if (st->bufptr >= st->bufend) { // Encrypt and flush - outbytes = (st->crypto_cb)(&st->crypto_ctx, temp, st->buffer, sizeof(st->buffer)); + outbytes = (st->crypto_cb)(&st->crypto_ctx, temp, (uint8_t *)st->buffer, sizeof(st->buffer)); if (!_pdfioFileWrite(st->pdf, temp, outbytes)) return (false); @@ -980,8 +1001,13 @@ stream_read(pdfio_stream_t *st, // I - Stream rbytes = _pdfioFileRead(st->pdf, buffer, bytes); if (rbytes > 0) + { st->remaining -= (size_t)rbytes; + if (st->crypto_cb) + (st->crypto_cb)(&st->crypto_ctx, (uint8_t *)buffer, (uint8_t *)buffer, rbytes); + } + return (rbytes); } else if (st->filter == PDFIO_FILTER_FLATE) @@ -1005,6 +1031,9 @@ stream_read(pdfio_stream_t *st, // I - Stream if (rbytes <= 0) return (-1); // End of file... + if (st->crypto_cb) + rbytes = (st->crypto_cb)(&st->crypto_ctx, st->cbuffer, st->cbuffer, rbytes); + st->remaining -= (size_t)rbytes; st->flate.next_in = (Bytef *)st->cbuffer; st->flate.avail_in = (uInt)rbytes; @@ -1058,6 +1087,9 @@ stream_read(pdfio_stream_t *st, // I - Stream if (rbytes <= 0) return (-1); // End of file... + if (st->crypto_cb) + rbytes = (st->crypto_cb)(&st->crypto_ctx, st->cbuffer, st->cbuffer, rbytes); + st->remaining -= (size_t)rbytes; st->flate.next_in = (Bytef *)st->cbuffer; st->flate.avail_in = (uInt)rbytes; @@ -1122,6 +1154,9 @@ stream_read(pdfio_stream_t *st, // I - Stream if (rbytes <= 0) return (-1); // End of file... + if (st->crypto_cb) + rbytes = (st->crypto_cb)(&st->crypto_ctx, st->cbuffer, st->cbuffer, rbytes); + st->remaining -= (size_t)rbytes; st->flate.next_in = (Bytef *)st->cbuffer; st->flate.avail_in = (uInt)rbytes; diff --git a/pdfio.h b/pdfio.h index cf6397c..35951d9 100644 --- a/pdfio.h +++ b/pdfio.h @@ -196,7 +196,7 @@ extern size_t pdfioFileGetNumObjs(pdfio_file_t *pdf) _PDFIO_PUBLIC; extern size_t pdfioFileGetNumPages(pdfio_file_t *pdf) _PDFIO_PUBLIC; extern pdfio_obj_t *pdfioFileGetObj(pdfio_file_t *pdf, size_t n) _PDFIO_PUBLIC; extern pdfio_obj_t *pdfioFileGetPage(pdfio_file_t *pdf, size_t n) _PDFIO_PUBLIC; -extern pdfio_permission_t pdfioFileGetPermissions(pdfio_file_t *pdf) _PDFIO_PUBLIC; +extern pdfio_permission_t pdfioFileGetPermissions(pdfio_file_t *pdf, pdfio_encryption_t *encryption) _PDFIO_PUBLIC; extern const char *pdfioFileGetProducer(pdfio_file_t *pdf) _PDFIO_PUBLIC; extern const char *pdfioFileGetSubject(pdfio_file_t *pdf) _PDFIO_PUBLIC; extern const char *pdfioFileGetTitle(pdfio_file_t *pdf) _PDFIO_PUBLIC;