From 038046e6d5ee88275633dd93db5c51c3ae018108 Mon Sep 17 00:00:00 2001 From: Michael R Sweet Date: Mon, 25 Oct 2021 19:36:39 -0400 Subject: [PATCH] Save work on encrypted PDF reading. --- pdfio-crypto.c | 503 ++++++++++++++++++++++++++++++++++++++---------- pdfio-file.c | 45 +++-- pdfio-private.h | 4 +- pdfio-token.c | 3 +- testpdfio.c | 33 +++- 5 files changed, 456 insertions(+), 132 deletions(-) diff --git a/pdfio-crypto.c b/pdfio-crypto.c index 347063c..0591642 100644 --- a/pdfio-crypto.c +++ b/pdfio-crypto.c @@ -23,6 +23,61 @@ #endif // __has_include +// +// PDF files can use one of several methods to encrypt a PDF file. There is +// an owner password that controls/unlocks full editing/usage permissions and a +// user password that unlocks limited usage of the PDF. Permissions are set +// using bits for copy, print, etc. (see the `pdfio_permission_t` enumeration). +// Passwords can be up to 32 bytes in length, with a well-known padding string +// that is applied if the string is less than 32 bytes or there is no password. +// +// > Note: PDF encryption has several design weaknesses which limit the +// > protection offered. The V2 and V4 security handlers depend on the obsolete +// > MD5 and RC4 algorithms for key generation, and Cipher Block Chaining (CBC) +// > further weakens AES support. Enforcement of usage permissions depends on +// > the consuming software honoring them, so if the password is known or (more +// > commonly) the user password is blank, it is possible to bypass usage +// > permissions completely. +// +// PDFio supports the following: +// +// - The original 40-bit RC4 (V2+R2) encryption for reading only +// - 128-bit RC4 (V2+R3) encryption for reading and writing +// - 128-bit AES (V4+R4) encryption for reading and writing +// - TODO: 256-bit AES (V6+R6) encryption for reading and writing +// +// Common values: +// +// - "F" is the file encryption key (40 to 256 bits/5 to 32 bytes) +// - "Fid" is the file ID string (stored in PDF file, 32 bytes) +// - "O" is the owner key (stored in PDF file, 32 bytes) +// - "Opad" is the padded owner password (32 bytes) +// - "P" is the permissions integer (stored in PDF file) +// - "P4" is the permissions integer as a 32-bit little-endian value +// - "U" is the user key (stored in PDF file, 32 bytes) +// - "Upad" is the padded user password (32 bytes) +// +// V2+R2 handler: +// +// F = md5(Upad+O+P4+Fid) +// O = rc4(Upad, md5(Opad)) +// (unlock with owner password) +// Upad = rc4(O, md5(Opad)) +// U = rc4(md5(Upad+Fid)+0[16], F) +// +// V2+R3/V4+R4 handler: +// +// F = md5(md5(Upad+O+P4+Fid))^50 +// O = rc4(Upad, md5(md5(Opad))^50)^20 +// (unlock with owner password) +// Upad = rc4(O, md5(md5(Opad))^50)^20 +// U = rc4(md5(Upad+Fid)+0[16], F)^20 +// +// V6+R6 handler: +// +// TODO: document V6+R6 handler +// + // // Local globals... // @@ -36,6 +91,18 @@ static uint8_t pdf_passpad[32] = // Padding for passwords }; +// +// Local functions... +// + +static void decrypt_user_key(pdfio_encryption_t encryption, const uint8_t *file_key, uint8_t user_key[32]); +static void encrypt_user_key(pdfio_encryption_t encryption, const uint8_t *file_key, uint8_t user_key[32]); +static void make_file_key(pdfio_encryption_t encryption, pdfio_permission_t permissions, const unsigned char *file_id, size_t file_idlen, const uint8_t *user_pad, const uint8_t *owner_key, uint8_t file_key[16]); +static void make_owner_key(pdfio_encryption_t encryption, const uint8_t *owner_pad, const uint8_t *user_pad, uint8_t owner_key[32]); +static void make_user_key(pdfio_encryption_t encryption, const unsigned char *file_id, size_t file_idlen, uint8_t user_key[32]); +static void pad_password(const char *password, uint8_t pad[32]); + + // // '_pdfioCryptoLock()' - Lock a PDF file by generating the encryption object and keys. // @@ -58,7 +125,7 @@ _pdfioCryptoLock( 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 + size_t file_idlen; // Length of file ID pdfio_dict_t *cf_dict, // CF dictionary *filter_dict; // CryptFilter dictionary @@ -76,118 +143,32 @@ _pdfioCryptoLock( 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; - } + pad_password(user_password, user_pad); - 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) + if (!owner_password && user_password && *user_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; + // Use supplied owner password + pad_password(owner_password, owner_pad); } - 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)); - } + make_owner_key(encryption, owner_pad, user_pad, 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_idlen); - 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; + make_file_key(encryption, permissions, file_id, file_idlen, user_pad, pdf->owner_key, pdf->file_key); + pdf->file_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)); - } + make_user_key(encryption, file_id, file_idlen, pdf->user_key); + encrypt_user_key(encryption, pdf->file_key, pdf->user_key); pdf->user_keylen = 32; // Save everything in the dictionary... @@ -451,7 +432,7 @@ _pdfio_crypto_cb_t // O - Decryption callback or `NULL` for none case PDFIO_ENCRYPTION_RC4_128 : case PDFIO_ENCRYPTION_AES_128 : // Copy the key data for the MD5 hash. - memcpy(data, pdf->encryption_key, sizeof(pdf->encryption_key)); + memcpy(data, pdf->file_key, sizeof(pdf->file_key)); data[16] = (uint8_t)obj->number; data[17] = (uint8_t)(obj->number >> 8); data[18] = (uint8_t)(obj->number >> 16); @@ -515,7 +496,7 @@ _pdfio_crypto_cb_t // O - Encryption callback or `NULL` for none case PDFIO_ENCRYPTION_RC4_128 : case PDFIO_ENCRYPTION_AES_128 : // Copy the key data for the MD5 hash. - memcpy(data, pdf->encryption_key, sizeof(pdf->encryption_key)); + memcpy(data, pdf->file_key, sizeof(pdf->file_key)); data[16] = (uint8_t)obj->number; data[17] = (uint8_t)(obj->number >> 8); data[18] = (uint8_t)(obj->number >> 16); @@ -573,6 +554,8 @@ _pdfioCryptoUnlock( size_t owner_keylen, // Length of owner key user_keylen, // Length of user key file_idlen; // Length of file ID + _pdfio_md5_t md5; // MD5 context + uint8_t file_digest[16]; // MD5 digest of file ID and pad // See if we support the type of encryption specified by the Encrypt object @@ -641,8 +624,8 @@ _pdfioCryptoUnlock( } // Grab the remaining values we need to unlock the PDF... - pdf->encryption_keylen = length / 8; - pdf->permissions = pdfioDictGetNumber(encrypt_dict, "P"); + pdf->file_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); @@ -670,12 +653,328 @@ _pdfioCryptoUnlock( _pdfioFileError(pdf, "Missing or bad file ID, unable to unlock file."); return (false); } + + // Generate a base hash from known values... + _pdfioCryptoMD5Init(&md5); + _pdfioCryptoMD5Append(&md5, pdf_passpad, 32); + _pdfioCryptoMD5Append(&md5, file_id, file_idlen); + _pdfioCryptoMD5Finish(&md5, file_digest); // Now try to unlock the PDF... for (tries = 0; tries < 4; tries ++) { - // + if (pdf->encryption <= PDFIO_ENCRYPTION_AES_128) + { + uint8_t pad[32], // Padded password + file_key[16], // File key + owner_key[32], // Owner key + user_pad[32], // Padded user password + user_key[32], // User key + pdf_user_key[32]; // Decrypted user key + + // Pad the supplied password, if any... + pad_password(password, pad); + + // Generate keys to see if things match... + PDFIO_DEBUG("\nTrying %02X%02X%02X%02X...%02X%02X%02X%02X\n", pad[0], pad[1], pad[2], pad[3], pad[28], pad[29], pad[30], pad[31]); + PDFIO_DEBUG("P=%d\n", pdf->permissions); + PDFIO_DEBUG("Fid(%d)=%02X%02X%02X%02X...%02X%02X%02X%02X\n", (int)file_idlen, file_id[0], file_id[1], file_id[2], file_id[3], file_id[12], file_id[13], file_id[14], file_id[15]); + + make_owner_key(pdf->encryption, pad, pdf->owner_key, user_pad); + PDFIO_DEBUG("Upad=%02X%02X%02X%02X...%02X%02X%02X%02X\n", user_pad[0], user_pad[1], user_pad[2], user_pad[3], user_pad[28], user_pad[29], user_pad[30], user_pad[31]); + + make_file_key(pdf->encryption, pdf->permissions, file_id, file_idlen, user_pad, pdf->owner_key, file_key); + PDFIO_DEBUG("Fown=%02X%02X%02X%02X...%02X%02X%02X%02X\n", file_key[0], file_key[1], file_key[2], file_key[3], file_key[12], file_key[13], file_key[14], file_key[15]); + + make_user_key(pdf->encryption, file_id, file_idlen, user_key); + + PDFIO_DEBUG("U=%02X%02X%02X%02X...%02X%02X%02X%02X\n", pdf->user_key[0], pdf->user_key[1], pdf->user_key[2], pdf->user_key[3], pdf->user_key[28], pdf->user_key[29], pdf->user_key[30], pdf->user_key[31]); + PDFIO_DEBUG("Uown=%02X%02X%02X%02X...%02X%02X%02X%02X\n", user_key[0], user_key[1], user_key[2], user_key[3], user_key[28], user_key[29], user_key[30], user_key[31]); + + if (!memcmp(user_key, pdf->user_key, sizeof(user_key))) + { + // Matches! + memcpy(pdf->file_key, file_key, sizeof(pdf->file_key)); + return (true); + } + + /* + * Not the owner password, try the user password... + */ + + make_file_key(pdf->encryption, pdf->permissions, file_id, file_idlen, pad, pdf->owner_key, file_key); + PDFIO_DEBUG("Fuse=%02X%02X%02X%02X...%02X%02X%02X%02X\n", file_key[0], file_key[1], file_key[2], file_key[3], file_key[12], file_key[13], file_key[14], file_key[15]); + + make_user_key(pdf->encryption, file_id, file_idlen, user_key); + + memcpy(pdf_user_key, pdf->user_key, sizeof(pdf_user_key)); + decrypt_user_key(pdf->encryption, file_key, pdf_user_key); + + PDFIO_DEBUG("Uuse=%02X%02X%02X%02X...%02X%02X%02X%02X\n", user_key[0], user_key[1], user_key[2], user_key[3], user_key[28], user_key[29], user_key[30], user_key[31]); + PDFIO_DEBUG("Updf=%02X%02X%02X%02X...%02X%02X%02X%02X\n", pdf_user_key[0], pdf_user_key[1], pdf_user_key[2], pdf_user_key[3], pdf_user_key[28], pdf_user_key[29], pdf_user_key[30], pdf_user_key[31]); + + if (!memcmp(user_key, pdf_user_key, 16)) + { + // Matches! + memcpy(pdf->file_key, file_key, sizeof(pdf->file_key)); + return (true); + } + } + else + { + // TODO: Implement AES-256 security handler + } + + // If we get here we need to try another password... + if (password_cb) + password = (password_cb)(password_data, pdf->filename); + + if (!password) + break; } + _pdfioFileError(pdf, "Unable to unlock PDF file."); + return (false); } + + +// +// 'decrypt_user_key()' - Decrypt the user key. +// + +static void +decrypt_user_key( + pdfio_encryption_t encryption, // I - Type of encryption + const uint8_t *file_key, // I - File encryption key + uint8_t user_key[32]) // IO - User key +{ + size_t i, j; // Looping vars + _pdfio_rc4_t rc4; // RC4 encryption context + + + if (encryption == PDFIO_ENCRYPTION_RC4_40) + { + // Encrypt the result once... + _pdfioCryptoRC4Init(&rc4, file_key, 5); + _pdfioCryptoRC4Crypt(&rc4, user_key, user_key, 32); + } + else + { + // Encrypt the result 20 times... + uint8_t key[16]; // Current encryption key + + for (i = 19; i > 0; i --) + { + // XOR each byte in the key with the loop counter... + for (j = 0; j < 16; j ++) + key[j] = (uint8_t)(file_key[j] ^ i); + + _pdfioCryptoRC4Init(&rc4, key, 16); + _pdfioCryptoRC4Crypt(&rc4, user_key, user_key, 32); + } + + _pdfioCryptoRC4Init(&rc4, file_key, 16); + _pdfioCryptoRC4Crypt(&rc4, user_key, user_key, 32); + } +} + + +// +// 'encrypt_user_key()' - Encrypt the user key. +// + +static void +encrypt_user_key( + pdfio_encryption_t encryption, // I - Type of encryption + const uint8_t *file_key, // I - File encryption key + uint8_t user_key[32]) // IO - User key +{ + size_t i, j; // Looping vars + _pdfio_rc4_t rc4; // RC4 encryption context + + + if (encryption == PDFIO_ENCRYPTION_RC4_40) + { + // Encrypt the result once... + _pdfioCryptoRC4Init(&rc4, file_key, 5); + _pdfioCryptoRC4Crypt(&rc4, user_key, user_key, 32); + } + else + { + // Encrypt the result 20 times... + uint8_t key[16]; // Current encryption key + + for (i = 0; i < 20; i ++) + { + // XOR each byte in the key with the loop counter... + for (j = 0; j < 16; j ++) + key[j] = (uint8_t)(file_key[j] ^ i); + + _pdfioCryptoRC4Init(&rc4, key, 16); + _pdfioCryptoRC4Crypt(&rc4, user_key, user_key, 32); + } + } +} + + +// +// 'make_file_key()' - Make the file encryption key. +// + +static void +make_file_key( + pdfio_encryption_t encryption, // I - Type of encryption + pdfio_permission_t permissions, // I - File permissions + const unsigned char *file_id, // I - File ID value + size_t file_idlen, // I - Length of file ID + const uint8_t *user_pad, // I - Padded user password + const uint8_t *owner_key, // I - Owner key + uint8_t file_key[16]) // O - Encryption key +{ + size_t i, j; // Looping vars + uint8_t perm_bytes[4]; // Permissions bytes + _pdfio_md5_t md5; // MD5 context + uint8_t digest[16]; // 128-bit MD5 digest + _pdfio_rc4_t rc4; // RC4 encryption context + + + 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); + + _pdfioCryptoMD5Init(&md5); + _pdfioCryptoMD5Append(&md5, user_pad, 32); + _pdfioCryptoMD5Append(&md5, owner_key, 32); + _pdfioCryptoMD5Append(&md5, perm_bytes, 4); + _pdfioCryptoMD5Append(&md5, file_id, file_idlen); + _pdfioCryptoMD5Finish(&md5, digest); + + if (encryption != PDFIO_ENCRYPTION_RC4_40) + { + // MD5 the result 50 times.. + for (i = 0; i < 50; i ++) + { + _pdfioCryptoMD5Init(&md5); + _pdfioCryptoMD5Append(&md5, digest, 16); + _pdfioCryptoMD5Finish(&md5, digest); + } + } + + memcpy(file_key, digest, 16); +} + + +// +// 'make_owner_key()' - Generate the (encrypted) owner key... +// + +static void +make_owner_key( + pdfio_encryption_t encryption, // I - Type of encryption + const uint8_t *owner_pad, // I - Padded owner password + const uint8_t *user_pad, // I - Padded user password + uint8_t owner_key[32]) // O - Owner key value +{ + 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 + + + // Hash the owner password... + _pdfioCryptoMD5Init(&md5); + _pdfioCryptoMD5Append(&md5, owner_pad, 32); + _pdfioCryptoMD5Finish(&md5, digest); + + if (encryption != PDFIO_ENCRYPTION_RC4_40) + { + for (i = 0; i < 50; i ++) + { + _pdfioCryptoMD5Init(&md5); + _pdfioCryptoMD5Append(&md5, digest, 16); + _pdfioCryptoMD5Finish(&md5, digest); + } + } + + // Copy and encrypt the padded user password... + memcpy(owner_key, user_pad, 32); + + if (encryption == PDFIO_ENCRYPTION_RC4_40) + { + // Encrypt once... + _pdfioCryptoRC4Init(&rc4, digest, 5); + _pdfioCryptoRC4Crypt(&rc4, owner_key, owner_key, 32); + } + else + { + // Encrypt 20 times... + uint8_t encrypt_key[16]; // RC4 encryption key + + for (i = 0; i < 20; i ++) + { + // 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, owner_key, owner_key, 32); + } + } +} + + +// +// 'make_user_key()' - Make the user key. +// + +static void +make_user_key( + pdfio_encryption_t encryption, // I - Type of encryption + const unsigned char *file_id, // I - File ID value + size_t file_idlen, // I - Length of file ID + uint8_t user_key[32]) // O - User key +{ + _pdfio_md5_t md5; // MD5 context + uint8_t digest[16]; // 128-bit MD5 digest + + + // Generate a base hash from known values... + _pdfioCryptoMD5Init(&md5); + _pdfioCryptoMD5Append(&md5, pdf_passpad, 32); + _pdfioCryptoMD5Append(&md5, file_id, file_idlen); + _pdfioCryptoMD5Finish(&md5, user_key); + + memset(user_key + 16, 0, 16); +} + + +// +// 'pad_password()' - Generate a padded password. +// + +static void +pad_password(const char *password, // I - Password string or `NULL` + uint8_t pad[32]) // O - Padded password +{ + size_t len; // Length of password + + + if (password) + { + // Use the specified password + if ((len = strlen(password)) > 32) + len = 32; + } + else + { + // No password + len = 0; + } + + if (len > 0) + memcpy(pad, password, len); + if (len < 32) + memcpy(pad + len, pdf_passpad, 32 - len); +} diff --git a/pdfio-file.c b/pdfio-file.c index 5453cfa..ac9e2dc 100644 --- a/pdfio-file.c +++ b/pdfio-file.c @@ -1637,9 +1637,19 @@ load_xref( 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); + if (!pdf->trailer_dict) + { + // Save the trailer dictionary and grab the root (catalog) and info + // objects... + pdf->trailer_dict = trailer.value.dict; + pdf->info_obj = pdfioDictGetObj(pdf->trailer_dict, "Info"); + pdf->encrypt_obj = pdfioDictGetObj(pdf->trailer_dict, "Encrypt"); + pdf->id_array = pdfioDictGetArray(pdf->trailer_dict, "ID"); + + // If the trailer contains an Encrypt key, try unlocking the file... + if (pdf->encrypt_obj && !_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); @@ -1750,6 +1760,20 @@ load_xref( } _pdfioTokenFlush(&tb); + + if (!pdf->trailer_dict) + { + // Save the trailer dictionary and grab the root (catalog) and info + // objects... + pdf->trailer_dict = trailer.value.dict; + pdf->info_obj = pdfioDictGetObj(pdf->trailer_dict, "Info"); + pdf->encrypt_obj = pdfioDictGetObj(pdf->trailer_dict, "Encrypt"); + pdf->id_array = pdfioDictGetArray(pdf->trailer_dict, "ID"); + + // If the trailer contains an Encrypt key, try unlocking the file... + if (pdf->encrypt_obj && !_pdfioCryptoUnlock(pdf, password_cb, password_data)) + return (false); + } } else { @@ -1762,17 +1786,6 @@ load_xref( PDFIO_DEBUG_VALUE(&trailer); PDFIO_DEBUG("\n"); - if (!pdf->trailer_dict) - { - // 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) done = true; } @@ -1787,10 +1800,6 @@ load_xref( PDFIO_DEBUG("load_xref: Root=%p(%lu)\n", pdf->root_obj, (unsigned long)pdf->root_obj->number); - pdf->info_obj = pdfioDictGetObj(pdf->trailer_dict, "Info"); - pdf->encrypt_obj = pdfioDictGetObj(pdf->trailer_dict, "Encrypt"); - pdf->id_array = pdfioDictGetArray(pdf->trailer_dict, "ID"); - return (load_pages(pdf, pdfioDictGetObj(pdfioObjGetDict(pdf->root_obj), "Pages"))); } diff --git a/pdfio-private.h b/pdfio-private.h index ef79056..17e6202 100644 --- a/pdfio-private.h +++ b/pdfio-private.h @@ -256,10 +256,10 @@ struct _pdfio_file_s // PDF file structure pdfio_encryption_t encryption; // Encryption mode pdfio_permission_t permissions; // Access permissions (encrypted PDF files) - uint8_t encryption_key[16], // Object encryption key + uint8_t file_key[16], // File encryption key owner_key[32], // Owner encryption key user_key[32]; // User encryption key - size_t encryption_keylen, // Length of encryption key + size_t file_keylen, // Length of file encryption key owner_keylen, // Length of owner encryption key user_keylen; // Length of user encryption key diff --git a/pdfio-token.c b/pdfio-token.c index 35ce9d1..6cb79c8 100644 --- a/pdfio-token.c +++ b/pdfio-token.c @@ -454,7 +454,7 @@ _pdfioTokenRead(_pdfio_token_t *tb, // I - Token buffer/stack return (false); } - while ((ch = get_char(tb)) != EOF && ch != '>') + do { if (isxdigit(ch)) { @@ -476,6 +476,7 @@ _pdfioTokenRead(_pdfio_token_t *tb, // I - Token buffer/stack return (false); } } + while ((ch = get_char(tb)) != EOF && ch != '>'); if (ch == EOF) { diff --git a/testpdfio.c b/testpdfio.c index 832f306..3db591f 100644 --- a/testpdfio.c +++ b/testpdfio.c @@ -35,6 +35,7 @@ static int do_unit_tests(void); static int draw_image(pdfio_stream_t *st, const char *name, double x, double y, double w, double h, const char *label); static bool error_cb(pdfio_file_t *pdf, const char *message, bool *error); static ssize_t output_cb(int *fd, const void *buffer, size_t bytes); +static const char *password_cb(void *data, const char *filename); static int read_unit_file(const char *filename, size_t num_pages, size_t first_image, bool is_output); static ssize_t token_consume_cb(const char **s, size_t bytes); static ssize_t token_peek_cb(const char **s, char *buffer, size_t bytes); @@ -1048,8 +1049,8 @@ do_unit_tests(void) 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); + if (read_unit_file("testpdfio-rc4.pdf", num_pages, first_image, false)) + return (1); // Create new encrypted PDF files... fputs("pdfioFileCreate(\"testpdfio-rc4p.pdf\", ...): ", stdout); @@ -1067,8 +1068,8 @@ do_unit_tests(void) if (write_unit_file(inpdf, outpdf, &num_pages, &first_image)) return (1); -// if (read_unit_file("testpdfio-rc4p.pdf", num_pages, first_image, false)) -// return (1); + if (read_unit_file("testpdfio-rc4p.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) @@ -1085,8 +1086,8 @@ do_unit_tests(void) 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); + if (read_unit_file("testpdfio-aes.pdf", num_pages, first_image, false)) + return (1); fputs("pdfioFileCreate(\"testpdfio-aesp.pdf\", ...): ", stdout); if ((outpdf = pdfioFileCreate("testpdfio-aesp.pdf", NULL, NULL, NULL, (pdfio_error_cb_t)error_cb, &error)) != NULL) @@ -1103,8 +1104,8 @@ do_unit_tests(void) if (write_unit_file(inpdf, outpdf, &num_pages, &first_image)) return (1); -// if (read_unit_file("testpdfio-aesp.pdf", num_pages, first_image, false)) -// return (1); + if (read_unit_file("testpdfio-aesp.pdf", num_pages, first_image, false)) + return (1); return (0); } @@ -1203,6 +1204,20 @@ output_cb(int *fd, // I - File descriptor } +// +// 'password_cb()' - Password callback for PDF file. +// + +static const char * // O - Password string +password_cb(void *data, // I - Callback data + const char *filename) // I - Filename (not used) +{ + (void)filename; + + return ((const char *)data); +} + + // // 'read_unit_file()' - Read back a unit test file and confirm its contents. // @@ -1220,7 +1235,7 @@ read_unit_file(const char *filename, // I - File to read // Open the new PDF file to read it... printf("pdfioFileOpen(\"%s\", ...): ", filename); - if ((pdf = pdfioFileOpen(filename, /*password_cb*/NULL, /*password_data*/NULL, (pdfio_error_cb_t)error_cb, &error)) != NULL) + if ((pdf = pdfioFileOpen(filename, password_cb, (void *)"user", (pdfio_error_cb_t)error_cb, &error)) != NULL) puts("PASS"); else return (1);