diff --git a/README.md b/README.md index e9a99ba..ba972c1 100644 --- a/README.md +++ b/README.md @@ -13,8 +13,7 @@ goals of PDFio are: - Read and write any version of PDF file - Provide access to pages, objects, and streams within a PDF file -- Support reading encrypted PDF files -- Support writing PDF files with digital signatures +- Support reading of encrypted PDF files - Extract or embed useful metadata (author, creator, page information, etc.) - "Filter" PDF files, for example to extract a range of pages or to embed fonts that are missing from a PDF diff --git a/pdfio-array.c b/pdfio-array.c index defe3f7..6f91699 100644 --- a/pdfio-array.c +++ b/pdfio-array.c @@ -389,7 +389,7 @@ pdfioArrayGetBinary( size_t n, // I - Index size_t *length) // O - Length of string { - if (!a || n >= a->num_values || a->values[n].type != PDFIO_VALTYPE_BINARY || !length) + if (!a || n >= a->num_values || a->values[n].type != PDFIO_VALTYPE_BINARY) { if (length) *length = 0; @@ -398,7 +398,9 @@ pdfioArrayGetBinary( } else { - *length = a->values[n].value.binary.datalen; + if (length) + *length = a->values[n].value.binary.datalen; + return (a->values[n].value.binary.data); } } diff --git a/pdfio-file.c b/pdfio-file.c index 62cbd6f..cae11e2 100644 --- a/pdfio-file.c +++ b/pdfio-file.c @@ -1034,13 +1034,219 @@ pdfioFileSetKeywords( } +// +// 'pdfioFileSetPermissions()' - Set the PDF permissions, encryption mode, and passwords. +// +// This function sets the PDF usage permissions, encryption mode, and +// passwords. +// +// > *Note*: This function must be called before creating or copying any +// > objects. Due to fundamental limitations in the PDF format, PDF encryption +// > offers little protection from disclosure. Permissions are not enforced in +// > any meaningful way. +// + +bool // O - `true` on success, `false` otherwise +pdfioFileSetPermissions( + 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 + 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); + + if (pdf->num_objs > 0) + { + _pdfioFileError(pdf, "You must call pdfioFileSetPermissions before adding any objects."); + return (false); + } + + 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) + { + // Copy the user password and pad it with the special PDF pad bytes + if ((len = strlen(user_password)) > sizeof(user_pad)) + len = sizeof(user_pad); + + if (len > 0) + memcpy(user_pad, user_password, len); + if (len < sizeof(user_pad)) + memcpy(user_pad + len, pad, sizeof(user_pad) - len); + } + else + { + // Use default (pad) password + memcpy(user_pad, pad, sizeof(user_pad)); + } + + if (owner_password && *owner_password) + { + // Copy the owner password and pad it with the special PDF pad bytes + if ((len = strlen(owner_password)) > sizeof(owner_pad)) + len = sizeof(owner_pad); + + if (len > 0) + memcpy(owner_pad, owner_password, len); + if (len < sizeof(owner_pad)) + memcpy(owner_pad + len, pad, sizeof(owner_pad) - len); + } + else + { + // Generate a random owner password... + _pdfioCryptoMakeRandom(owner_pad, sizeof(owner_pad)); + } + + // Compute the owner key... + _pdfioCryptoMD5Init(&md5); + _pdfioCryptoMD5Append(&md5, owner_pad, 32); + _pdfioCryptoMD5Finish(&md5, digest); + + // MD5 the result 50 more times... + for (i = 0; i < 50; i ++) + { + _pdfioCryptoMD5Init(&md5); + _pdfioCryptoMD5Append(&md5, digest, 16); + _pdfioCryptoMD5Finish(&md5, digest); + } + + // Copy the padded user password... + memcpy(pdf->owner_key, user_pad, sizeof(pdf->owner_key)); + + // Encrypt the result 20 times... + 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, 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, 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 ? 3 : 4); + pdfioDictSetBinary(dict, "U", pdf->user_key, sizeof(pdf->user_key)); + 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); +} + + // // 'pdfioFileSetSubject()' - Set the subject for a PDF file. // void -pdfioFileSetSubject(pdfio_file_t *pdf, // I - PDF file - const char *value)// I - Value +pdfioFileSetSubject( + pdfio_file_t *pdf, // I - PDF file + const char *value) // I - Value { if (pdf && pdf->info) pdfioDictSetString(pdf->info->value.value.dict, "Subject", pdfioStringCreate(pdf, value)); @@ -1702,9 +1908,9 @@ load_xref(pdfio_file_t *pdf, // I - PDF file PDFIO_DEBUG("load_xref: Root=%p(%lu)\n", pdf->root, (unsigned long)pdf->root->number); - pdf->info = pdfioDictGetObj(pdf->trailer, "Info"); - pdf->encrypt = pdfioDictGetObj(pdf->trailer, "Encrypt"); - pdf->id_array = pdfioDictGetArray(pdf->trailer, "ID"); + pdf->info = pdfioDictGetObj(pdf->trailer, "Info"); + pdf->encrypt_obj = pdfioDictGetObj(pdf->trailer, "Encrypt"); + pdf->id_array = pdfioDictGetArray(pdf->trailer, "ID"); return (load_pages(pdf, pdfioDictGetObj(pdfioObjGetDict(pdf->root), "Pages"))); } @@ -1819,8 +2025,8 @@ write_trailer(pdfio_file_t *pdf) // I - PDF file goto done; } - if (pdf->encrypt) - pdfioDictSetObj(pdf->trailer, "Encrypt", pdf->encrypt); + if (pdf->encrypt_obj) + pdfioDictSetObj(pdf->trailer, "Encrypt", pdf->encrypt_obj); if (pdf->id_array) pdfioDictSetArray(pdf->trailer, "ID", pdf->id_array); pdfioDictSetObj(pdf->trailer, "Info", pdf->info); diff --git a/pdfio-private.h b/pdfio-private.h index c97075f..3403f38 100644 --- a/pdfio-private.h +++ b/pdfio-private.h @@ -255,6 +255,12 @@ struct _pdfio_file_s // PDF file structure pdfio_error_cb_t error_cb; // Error callback void *error_data; // Data for error callback + pdfio_encryption_t encryption; // Encryption mode + pdfio_permission_t permissions; // Access permissions (encrypted PDF files) + uint8_t encryption_key[16], // Object encryption key + owner_key[32], // Owner encryption key + user_key[32]; // User encryption key + // Active file data int fd; // File descriptor char buffer[8192], // Read/write buffer @@ -265,7 +271,7 @@ struct _pdfio_file_s // PDF file structure pdfio_obj_t *root; // Root object/dictionary pdfio_obj_t *info; // Information object pdfio_obj_t *pages_root; // Root pages object - pdfio_obj_t *encrypt; // Encryption object/dictionary + pdfio_obj_t *encrypt_obj; // De/Encryption object/dictionary pdfio_obj_t *cp1252_obj, // CP1252 font encoding object *unicode_obj; // Unicode font encoding object pdfio_array_t *id_array; // ID array diff --git a/pdfio.h b/pdfio.h index 4bf9082..1bd4093 100644 --- a/pdfio.h +++ b/pdfio.h @@ -59,6 +59,14 @@ typedef struct _pdfio_file_s pdfio_file_t; // PDF file typedef bool (*pdfio_error_cb_t)(pdfio_file_t *pdf, const char *message, void *data); // Error callback +typedef enum pdfio_encryption_e // PDF encryption modes +{ + PDFIO_ENCRYPTION_NONE = 0, // No encryption + PDFIO_ENCRYPTION_RC4_40, // 40-bit RC4 encryption (PDF 1.3) + PDFIO_ENCRYPTION_RC4_128, // 128-bit RC4 encryption (PDF 1.4) + PDFIO_ENCRYPTION_AES_128, // 128-bit AES encryption (PDF 1.6) + PDFIO_ENCRYPTION_AES_256 // 256-bit AES encryption (PDF 2.0) +} pdfio_encryption_t; typedef enum pdfio_filter_e // Compression/decompression filters for streams { PDFIO_FILTER_NONE, // No filter @@ -78,6 +86,20 @@ typedef ssize_t (*pdfio_output_cb_t)(void *ctx, const void *data, size_t datalen // Output callback for pdfioFileCreateOutput typedef const char *(*pdfio_password_cb_t)(void *data, const char *filename); // Password callback for pdfioFileOpen +enum pdfio_permission_e // PDF permission bits +{ + PDFIO_PERMISSION_NONE = 0, // No permissions + PDFIO_PERMISSION_PRINT = 0x0004, // PDF allows printing + PDFIO_PERMISSION_MODIFY = 0x0008, // PDF allows modification + PDFIO_PERMISSION_COPY = 0x0010, // PDF allows copying + PDFIO_PERMISSION_ANNOTATE = 0x0020, // PDF allows annotation + PDFIO_PERMISSION_FORMS = 0x0100, // PDF allows filling in forms + PDFIO_PERMISSION_READING = 0x0200, // PDF allows screen reading/accessibility (deprecated in PDF 2.0) + PDFIO_PERMISSION_ASSEMBLE = 0x0400, // PDF allows assembly (insert, delete, or rotate pages, add document outlines and thumbnails) + 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 struct pdfio_rect_s // PDF rectangle { double x1; // Lower-left X coordinate @@ -174,6 +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 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; @@ -183,6 +206,7 @@ extern void pdfioFileSetAuthor(pdfio_file_t *pdf, const char *value) _PDFIO_PUB extern void pdfioFileSetCreationDate(pdfio_file_t *pdf, time_t value) _PDFIO_PUBLIC; extern void pdfioFileSetCreator(pdfio_file_t *pdf, const char *value) _PDFIO_PUBLIC; extern void pdfioFileSetKeywords(pdfio_file_t *pdf, const char *value) _PDFIO_PUBLIC; +extern bool pdfioFileSetPermissions(pdfio_file_t *pdf, pdfio_permission_t permissions, pdfio_encryption_t encryption, const char *owner_password, const char *user_password) _PDFIO_PUBLIC; extern void pdfioFileSetSubject(pdfio_file_t *pdf, const char *value) _PDFIO_PUBLIC; extern void pdfioFileSetTitle(pdfio_file_t *pdf, const char *value) _PDFIO_PUBLIC;