mirror of
https://github.com/michaelrsweet/pdfio.git
synced 2024-12-26 05:18:21 +01:00
Save work on encrypted PDF reading.
This commit is contained in:
parent
45c5a00252
commit
038046e6d5
503
pdfio-crypto.c
503
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);
|
||||
}
|
||||
|
45
pdfio-file.c
45
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")));
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
@ -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)
|
||||
{
|
||||
|
33
testpdfio.c
33
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);
|
||||
|
Loading…
Reference in New Issue
Block a user