diff options
Diffstat (limited to 'source/luametatex/source/libraries/pplib/ppcrypt.c')
-rw-r--r-- | source/luametatex/source/libraries/pplib/ppcrypt.c | 748 |
1 files changed, 748 insertions, 0 deletions
diff --git a/source/luametatex/source/libraries/pplib/ppcrypt.c b/source/luametatex/source/libraries/pplib/ppcrypt.c new file mode 100644 index 000000000..ce63e7cab --- /dev/null +++ b/source/luametatex/source/libraries/pplib/ppcrypt.c @@ -0,0 +1,748 @@ + +#include "utilmd5.h" +#include "utilsha.h" + +#include "pplib.h" + +/* crypt struct */ + +static ppcrypt * ppcrypt_create (ppheap *heap) +{ + ppcrypt *crypt; + crypt = (ppcrypt *)ppstruct_take(heap, sizeof(ppcrypt)); + memset(crypt, 0, sizeof(ppcrypt)); + return crypt; +} + +int ppcrypt_type (ppcrypt *crypt, ppname *cryptname, ppuint *length, int *cryptflags) +{ + ppdict *filterdict; + ppname *filtertype; + int cryptmd = 0, default256 = 0; + + if (crypt->map == NULL || (filterdict = ppdict_rget_dict(crypt->map, cryptname->data)) == NULL) + return 0; + if ((filtertype = ppdict_get_name(filterdict, "CFM")) == NULL) + return 0; + *cryptflags = 0; + if (ppname_is(filtertype, "V2")) + *cryptflags |= PPCRYPT_INFO_RC4; + else if (ppname_is(filtertype, "AESV2")) + *cryptflags |= PPCRYPT_INFO_AES; + else if (ppname_is(filtertype, "AESV3")) + *cryptflags |= PPCRYPT_INFO_AES, default256 = 1; + else + return 0; + /* pdf spec page. 134: /Length is said to be optional bit-length of the key, but it seems to be a mistake, as Acrobat + produces /Length key with bytes lengths, opposite to /Length key of the main encrypt dict. */ + if (length != NULL) + if (!ppdict_get_uint(filterdict, "Length", length)) + *length = (*cryptflags & PPCRYPT_INFO_RC4) ? 5 : (default256 ? 32 : 16); + /* one of metadata flags is set iff there is an explicit EncryptMetadata key */ + if (ppdict_get_bool(filterdict, "EncryptMetadata", &cryptmd)) + *cryptflags |= (cryptmd ? PPCRYPT_INFO_MD : PPCRYPT_INFO_NOMD); + return 1; +} + +/* V1..4 algorithms */ + +/* V1..4 unicode do PdfDocEncoding */ + +typedef struct { + uint32_t unicode; + uint32_t code; + uint32_t count; +} map_range_t; + +static const map_range_t unicode_to_pdf_doc_encoding_map[] = { + { 32, 32, 95 }, + { 161, 161, 12 }, + { 174, 174, 82 }, + { 305, 154, 1 }, + { 321, 149, 1 }, + { 322, 155, 1 }, + { 338, 150, 1 }, + { 339, 156, 1 }, + { 352, 151, 1 }, + { 353, 157, 1 }, + { 376, 152, 1 }, + { 381, 153, 1 }, + { 382, 158, 1 }, + { 402, 134, 1 }, + { 710, 26, 1 }, + { 711, 25, 1 }, + { 728, 24, 1 }, + { 729, 27, 1 }, + { 730, 30, 1 }, + { 731, 29, 1 }, + { 732, 31, 1 }, + { 733, 28, 1 }, + { 8211, 133, 1 }, + { 8212, 132, 1 }, + { 8216, 143, 3 }, + { 8220, 141, 2 }, + { 8222, 140, 1 }, + { 8224, 129, 2 }, + { 8226, 128, 1 }, + { 8230, 131, 1 }, + { 8240, 139, 1 }, + { 8249, 136, 2 }, + { 8260, 135, 1 }, + { 8364, 160, 1 }, + { 8482, 146, 1 }, + { 8722, 138, 1 }, + { 64257, 147, 2 } +}; + +#define unicode_to_pdf_doc_encoding_entries (sizeof(unicode_to_pdf_doc_encoding_map) / sizeof(map_range_t)) + +static int unicode_to_pdf_doc_encoding (uint32_t unicode, uint8_t *pcode) +{ + const map_range_t *left, *right, *mid; + + left = &unicode_to_pdf_doc_encoding_map[0]; + right = &unicode_to_pdf_doc_encoding_map[unicode_to_pdf_doc_encoding_entries - 1]; + for ( ; left <= right; ) + { + mid = left + ((right - left) / 2); + if (unicode > mid->unicode + mid->count - 1) + left = mid + 1; + else if (unicode < mid->unicode) + right = mid - 1; + else + { + *pcode = (uint8_t)(mid->code + (unicode - mid->unicode)); + return 1; + } + } + return 0; +} + +#define utf8_unicode2(p) (((p[0]&31)<<6)|(p[1]&63)) +#define utf8_unicode3(p) (((p[0]&15)<<12)|((p[1]&63)<<6)|(p[2]&63)) +#define utf8_unicode4(p) (((p[0]&7)<<18)|((p[1]&63)<<12)|((p[2]&63)<<6)|(p[3]&63)) + +#define utf8_get1(p, e, unicode) ((unicode = p[0]), p + 1) +#define utf8_get2(p, e, unicode) (p + 1 < e ? ((unicode = utf8_unicode2(p)), p + 2) : NULL) +#define utf8_get3(p, e, unicode) (p + 2 < e ? ((unicode = utf8_unicode3(p)), p + 3) : NULL) +#define utf8_get4(p, e, unicode) (p + 4 < e ? ((unicode = utf8_unicode3(p)), p + 4) : NULL) + +#define utf8_get(p, e, unicode) \ + (p[0] < 0x80 ? utf8_get1(p, e, unicode) : \ + p[0] < 0xC0 ? NULL : \ + p[0] < 0xE0 ? utf8_get2(p, e, unicode) : \ + p[0] < 0xF0 ? utf8_get3(p, e, unicode) : utf8_get4(p, e, unicode)) + +static int ppcrypt_password_encoding (uint8_t *password, size_t *passwordlength) +{ + uint8_t *p, newpassword[PPCRYPT_MAX_PASSWORD], *n; + const uint8_t *e; + uint32_t unicode; + + for (n = &newpassword[0], p = &password[0], e = p + *passwordlength; p < e; ++n) + { + p = utf8_get(p, e, unicode); + if (p == NULL) + return 0; + if (unicode_to_pdf_doc_encoding(unicode, n) == 0) + return 0; + } + *passwordlength = n - &newpassword[0]; + memcpy(password, newpassword, *passwordlength); + return 1; +} + +/* setup passwords */ + +static const uint8_t password_padding[] = { + 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 +}; + +static void ppcrypt_set_user_password (ppcrypt *crypt, const void *userpass, size_t userpasslength) +{ + crypt->userpasslength = userpasslength > PPCRYPT_MAX_PASSWORD ? PPCRYPT_MAX_PASSWORD : userpasslength; + memcpy(crypt->userpass, userpass, crypt->userpasslength); + if (crypt->algorithm_variant < 5) + { + if (ppcrypt_password_encoding(crypt->userpass, &crypt->userpasslength) == 0) + return; + if (crypt->userpasslength > 32) + crypt->userpasslength = 32; + else if (crypt->userpasslength < 32) + memcpy(&crypt->userpass[crypt->userpasslength], password_padding, 32 - crypt->userpasslength); + } + crypt->flags |= PPCRYPT_USER_PASSWORD; +} + +static void ppcrypt_set_owner_password (ppcrypt *crypt, const void *ownerpass, size_t ownerpasslength) +{ + crypt->ownerpasslength = ownerpasslength > PPCRYPT_MAX_PASSWORD ? PPCRYPT_MAX_PASSWORD : ownerpasslength; + memcpy(crypt->ownerpass, ownerpass, crypt->ownerpasslength); + if (crypt->algorithm_variant < 5) + { + if (ppcrypt_password_encoding(crypt->ownerpass, &crypt->ownerpasslength) == 0) + return; + if (crypt->ownerpasslength > 32) + crypt->ownerpasslength = 32; + else if (crypt->ownerpasslength < 32) + memcpy(&crypt->ownerpass[crypt->ownerpasslength], password_padding, 32 - crypt->ownerpasslength); + } + crypt->flags |= PPCRYPT_OWNER_PASSWORD; +} + +/* V1..4 retrieving user password from owner password and owner key (variant < 5) */ + +static void ppcrypt_user_password_from_owner_key (ppcrypt *crypt, const void *ownerkey, size_t ownerkeysize) +{ + uint8_t temp[16], rc4key[32], rc4key2[32]; + uint8_t i; + ppuint k; + md5_state md5; + + md5_digest_init(&md5); + md5_digest_add(&md5, crypt->ownerpass, 32); + md5_digest_get(&md5, rc4key, MD5_BYTES); + if (crypt->algorithm_revision >= 3) + { + for (i = 0; i < 50; ++i) + { + md5_digest(rc4key, 16, temp, MD5_BYTES); + memcpy(rc4key, temp, 16); + } + } + rc4_decode_data(ownerkey, ownerkeysize, crypt->userpass, rc4key, crypt->filekeylength); + if (crypt->algorithm_revision >= 3) + { + for (i = 1; i <= 19; ++i) + { + for (k = 0; k < crypt->filekeylength; ++k) + rc4key2[k] = rc4key[k] ^ i; + rc4_decode_data(crypt->userpass, 32, crypt->userpass, rc4key2, crypt->filekeylength); + } + } + //crypt->userpasslength = 32; + for (crypt->userpasslength = 0; crypt->userpasslength < 32; ++crypt->userpasslength) + if (memcmp(&crypt->userpass[crypt->userpasslength], password_padding, 32 - crypt->userpasslength) == 0) + break; + crypt->flags |= PPCRYPT_USER_PASSWORD; +} + +/* V1..4 generating file key; pdf spec p. 125 */ + +static void ppcrypt_compute_file_key (ppcrypt *crypt, const void *ownerkey, size_t ownerkeysize, const void *id, size_t idsize) +{ + uint32_t p; + uint8_t permissions[4], temp[16]; + int i; + md5_state md5; + + md5_digest_init(&md5); + md5_digest_add(&md5, crypt->userpass, 32); + md5_digest_add(&md5, ownerkey, ownerkeysize); + p = (uint32_t)crypt->permissions; + permissions[0] = get_number_byte1(p); + permissions[1] = get_number_byte2(p); + permissions[2] = get_number_byte3(p); + permissions[3] = get_number_byte4(p); + md5_digest_add(&md5, permissions, 4); + md5_digest_add(&md5, id, idsize); + if (crypt->algorithm_revision >= 4 && (crypt->flags & PPCRYPT_NO_METADATA)) + md5_digest_add(&md5, "\xFF\xFF\xFF\xFF", 4); + md5_digest_get(&md5, crypt->filekey, MD5_BYTES); + if (crypt->algorithm_revision >= 3) + { + for (i = 0; i < 50; ++i) + { + md5_digest(crypt->filekey, (size_t)crypt->filekeylength, temp, MD5_BYTES); + memcpy(crypt->filekey, temp, 16); + } + } +} + +/* V1..4 generating userkey for comparison with /U; requires a general file key and id; pdf spec page 126-127 */ + +static void ppcrypt_compute_user_key (ppcrypt *crypt, const void *id, size_t idsize, uint8_t password_hash[32]) +{ + uint8_t rc4key2[32]; + uint8_t i; + ppuint k; + + if (crypt->algorithm_revision <= 2) + { + rc4_encode_data(password_padding, 32, password_hash, crypt->filekey, crypt->filekeylength); + } + else + { + md5_state md5; + md5_digest_init(&md5); + md5_digest_add(&md5, password_padding, 32); + md5_digest_add(&md5, id, idsize); + md5_digest_get(&md5, password_hash, MD5_BYTES); + rc4_encode_data(password_hash, 16, password_hash, crypt->filekey, crypt->filekeylength); + for (i = 1; i <= 19; ++i) + { + for (k = 0; k < crypt->filekeylength; ++k) + rc4key2[k] = crypt->filekey[k] ^ i; + rc4_encode_data(password_hash, 16, password_hash, rc4key2, crypt->filekeylength); + } + for (i = 16; i < 32; ++i) + password_hash[i] = password_hash[i - 16] ^ i; /* arbitrary 16-bytes padding */ + } +} + +static ppcrypt_status ppcrypt_authenticate_legacy (ppcrypt *crypt, ppstring *userkey, ppstring *ownerkey, ppstring *id) +{ + uint8_t password_hash[32]; + + if ((crypt->flags & PPCRYPT_USER_PASSWORD) == 0 && (crypt->flags & PPCRYPT_OWNER_PASSWORD) != 0) + ppcrypt_user_password_from_owner_key(crypt, ownerkey, ownerkey->size); + ppcrypt_compute_file_key(crypt, ownerkey->data, ownerkey->size, id->data, id->size); + ppcrypt_compute_user_key(crypt, id->data, id->size, password_hash); /* needs file key */ + return memcmp(userkey->data, password_hash, (crypt->algorithm_revision >= 3 ? 16 : 32)) == 0 ? PPCRYPT_DONE : PPCRYPT_PASS; +} + +/* V5 */ + +static const uint8_t nulliv[16] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; /* AES-256 initialization vector */ + +/* V5 R5..6 password hash */ + +#define PPCRYPT_MAX_MANGLED ((127+64+48)*64) // 127 password, 64 hash, 48 /U key + +static void ppcrypt_password_hash_indeed (const uint8_t *password, size_t passwordlength, const uint8_t *userkey, uint8_t hash[64]) +{ + size_t hashlength, datalength; + uint8_t data[PPCRYPT_MAX_MANGLED], *pdata; + uint8_t *key, *iv; + uint8_t round, i; + uint32_t div3; + + hashlength = 32; /* initial hash is sha256 */ + round = 0; + do + { + /* concat password, hash, and /U value 64 times */ + pdata = &data[0]; + memcpy(pdata, password, passwordlength); + pdata += passwordlength; + memcpy(pdata, hash, hashlength); + pdata += hashlength; + if (userkey != NULL) + { + memcpy(pdata, userkey, 48); + pdata += 48; + } + datalength = pdata - &data[0]; + for (i = 1; i < 64; ++i, pdata += datalength) + memcpy(pdata, &data[0], datalength); + datalength *= 64; + + /* encrypt the data with aes128 using hash bytes 1..16 as key and bytes 17..32 as initialization vector + encryption inplace, CBC, no-padding, no change to datalength */ + key = &hash[0]; iv = &hash[16]; + aes_encode_data(data, datalength, data, key, 16, iv, AES_NULL_PADDING); + + /* get modulo 3 of first 16 bytes number of encrypted data (sum of digits modulo 3) */ + for (i = 0, div3 = 0; i < 16; ++i) + div3 += data[i]; + + /* compute new hash using sha256/384/512 */ + switch (div3 % 3) + { + case 0: + sha256_digest(data, datalength, hash, SHA_BYTES); + hashlength = 32; + break; + case 1: + sha384_digest(data, datalength, hash, SHA_BYTES); + hashlength = 48; + break; + case 2: + sha512_digest(data, datalength, hash, SHA_BYTES); + hashlength = 64; + break; + } + + /* do 64 times, then keep going until the last byte of data <= round - 32 */ + } while (++round < 64 || round < data[datalength - 1] + 32); + +} + +static void ppcrypt_password_hash (ppcrypt *crypt, const uint8_t *password, size_t passwordlength, const uint8_t *salt, const uint8_t *userkey, uint8_t password_hash[32]) +{ + sha256_state sha; + uint8_t hash[64]; /* result password_hash is 32 bytes, but we need 64 for R6 procedure */ + + /* take sha256 of password, salt and /U */ + sha256_digest_init(&sha); + sha256_digest_add(&sha, password, passwordlength); + sha256_digest_add(&sha, salt, 8); + if (userkey != NULL) + sha256_digest_add(&sha, userkey, 48); + sha256_digest_get(&sha, hash, SHA_BYTES); + + /* V5 R5 - password_hash is the digest, V5 R6 - password_hash is mangled */ + if (crypt->algorithm_revision >= 6) + ppcrypt_password_hash_indeed(password, passwordlength, userkey, hash); + + memcpy(password_hash, hash, 32); +} + +/* V5 permissions */ + +static ppcrypt_status ppcrypt_authenticate_permissions (ppcrypt *crypt, ppstring *perms) +{ + uint8_t permsdata[16]; + + aes_decode_data(perms->data, perms->size, permsdata, crypt->filekey, crypt->filekeylength, nulliv, AES_NULL_PADDING); + + if (permsdata[9] != 'a' || permsdata[10] != 'd' || permsdata[11] != 'b') + return PPCRYPT_FAIL; + + /* do not check/update permissions flags here; they might be different inside crypt string */ + if (0) + { + int64_t p; + int i; + for (p = 0, i = 0; i < 8; ++i) + p = p + (permsdata[i] << (i << 3)); /* low order bytes first */ + crypt->permissions = (ppint)((int32_t)(p & 0x00000000FFFFFFFFLL)); /* unset bits 33..64, treat as 32-bit signed int */ + } + + if (permsdata[8] == 'T') + crypt->flags &= ~PPCRYPT_NO_METADATA; + else if (permsdata[8] == 'F') + crypt->flags |= PPCRYPT_NO_METADATA; + + return PPCRYPT_DONE; +} + +/* V5 authentication */ + +static ppcrypt_status ppcrypt_authenticate_user (ppcrypt *crypt, ppstring *u, ppstring *ue, ppstring *perms) +{ + uint8_t password_hash[32], *salt; + + salt = (uint8_t *)&u->data[32]; /* validation salt */ + ppcrypt_password_hash(crypt, crypt->userpass, crypt->userpasslength, salt, NULL, password_hash); + if (memcmp(u->data, password_hash, 32) != 0) + return PPCRYPT_PASS; + + salt = (uint8_t *)&u->data[40]; /* key salt */ + ppcrypt_password_hash(crypt, crypt->userpass, crypt->userpasslength, salt, NULL, password_hash); + aes_decode_data(ue->data, 32, crypt->filekey, password_hash, 32, nulliv, AES_NULL_PADDING); + + return ppcrypt_authenticate_permissions(crypt, perms); +} + +static ppcrypt_status ppcrypt_authenticate_owner (ppcrypt *crypt, ppstring *u, ppstring *o, ppstring *oe, ppstring *perms) +{ + uint8_t password_hash[32], *salt; + + salt = (uint8_t *)&o->data[32]; /* validation salt */ + ppcrypt_password_hash(crypt, crypt->ownerpass, crypt->ownerpasslength, salt, (uint8_t *)u->data, password_hash); + if (memcmp(o->data, password_hash, 32) != 0) + return PPCRYPT_PASS; + + salt = (uint8_t *)&o->data[40]; /* key salt */ + ppcrypt_password_hash(crypt, crypt->ownerpass, crypt->ownerpasslength, salt, (uint8_t *)u->data, password_hash); + aes_decode_data(oe->data, 32, crypt->filekey, password_hash, 32, nulliv, AES_NULL_PADDING); + + return ppcrypt_authenticate_permissions(crypt, perms); +} + + +/* authentication */ + +static ppcrypt_status ppcrypt_authenticate (ppcrypt *crypt, ppstring *u, ppstring *ue, ppstring *o, ppstring *oe, ppstring *id, ppstring *perms) +{ + /* V1..V4 */ + if (crypt->algorithm_variant < 5) + return ppcrypt_authenticate_legacy(crypt, u, o, id); + + /* V5 */ + if (crypt->flags & PPCRYPT_USER_PASSWORD) + if (ppcrypt_authenticate_user(crypt, u, ue, perms) == PPCRYPT_DONE) + return PPCRYPT_DONE; + if (crypt->flags & PPCRYPT_OWNER_PASSWORD) + return ppcrypt_authenticate_owner(crypt, u, o, oe, perms); + + return PPCRYPT_PASS; +} + +/**/ + +ppcrypt_status ppdoc_crypt_init (ppdoc *pdf, const void *userpass, size_t userpasslength, const void *ownerpass, size_t ownerpasslength) +{ + ppcrypt *crypt; + ppdict *trailer, *encrypt; + ppobj *obj; + ppname *name, **pkey; + ppstring *userkey, *ownerkey, *userkey_e = NULL, *ownerkey_e = NULL; + size_t hashlength; + pparray *idarray; + ppstring *id = NULL, *perms = NULL; + int cryptflags, encryptmd; + size_t strkeylength, stmkeylength; + + trailer = ppxref_trailer(pdf->xref); + if ((obj = ppdict_get_obj(trailer, "Encrypt")) == NULL) + return PPCRYPT_NONE; + + /* this happens early, before loading body, so if /Encrypt is indirect reference, it points nothing */ + obj = ppobj_preloaded(pdf, obj); + if (obj->type != PPDICT) + return PPCRYPT_FAIL; + encrypt = obj->dict; + for (ppdict_first(encrypt, pkey, obj); *pkey != NULL; ppdict_next(pkey, obj)) + (void)ppobj_preloaded(pdf, obj); + + if ((name = ppdict_get_name(encrypt, "Filter")) != NULL && !ppname_is(name, "Standard")) + return PPCRYPT_FAIL; + + if ((crypt = pdf->crypt) == NULL) + crypt = pdf->crypt = ppcrypt_create(&pdf->heap); + + /* get /V /R /P */ + if (!ppdict_get_uint(encrypt, "V", &crypt->algorithm_variant)) + crypt->algorithm_variant = 0; + if (crypt->algorithm_variant < 1 || crypt->algorithm_variant > 5) + return PPCRYPT_FAIL; + if (!ppdict_get_uint(encrypt, "R", &crypt->algorithm_revision)) + return PPCRYPT_FAIL; + if (!ppdict_get_int(encrypt, "P", &crypt->permissions)) + return PPCRYPT_FAIL; + + /* get /O /U /ID /OE /UE */ + if ((userkey = ppdict_get_string(encrypt, "U")) == NULL || (ownerkey = ppdict_get_string(encrypt, "O")) == NULL) + return PPCRYPT_FAIL; + userkey = ppstring_decoded(userkey); + ownerkey = ppstring_decoded(ownerkey); + + /* for some reason acrobat pads /O and /U to 127 bytes with NULL, so we don't check the exact length but ensure the minimal */ + hashlength = crypt->algorithm_variant < 5 ? 32 : 48; + if (userkey->size < hashlength || ownerkey->size < hashlength) + return PPCRYPT_FAIL; + if (crypt->algorithm_variant < 5) + { // get first string from /ID (must not be ref) + if ((idarray = ppdict_get_array(trailer, "ID")) == NULL || (id = pparray_get_string(idarray, 0)) == NULL) + return PPCRYPT_FAIL; + id = ppstring_decoded(id); + } + else + { + if ((userkey_e = ppdict_get_string(encrypt, "UE")) == NULL || (ownerkey_e = ppdict_get_string(encrypt, "OE")) == NULL) + return PPCRYPT_FAIL; + userkey_e = ppstring_decoded(userkey_e); + ownerkey_e = ppstring_decoded(ownerkey_e); + if (userkey_e->size < 32 || ownerkey_e->size < 32) + return PPCRYPT_FAIL; + if ((perms = ppdict_get_string(encrypt, "Perms")) == NULL) + return PPCRYPT_FAIL; + perms = ppstring_decoded(perms); + if (perms->size != 16) + return PPCRYPT_FAIL; + } + + /* collect flags and keylength */ + switch (crypt->algorithm_revision) + { + case 1: + crypt->filekeylength = 5; + crypt->flags |= PPCRYPT_RC4; + break; + case 2: case 3: + if (ppdict_get_uint(encrypt, "Length", &crypt->filekeylength)) + crypt->filekeylength >>= 3; /* 40..256 bits, 5..32 bytes*/ + else + crypt->filekeylength = 5; /* 40 bits, 5 bytes */ + crypt->flags |= PPCRYPT_RC4; + break; + case 4: case 5: case 6: + if ((crypt->map = ppdict_rget_dict(encrypt, "CF")) == NULL) + return PPCRYPT_FAIL; + for (ppdict_first(crypt->map, pkey, obj); *pkey != NULL; ppdict_next(pkey, obj)) + (void)ppobj_preloaded(pdf, obj); + /* /EncryptMetadata relevant only for version >=4, may be also provided in crypt filter dictionary; which takes a precedence then? + we assume that if there is an explicit EncryptMetadata key, it overrides main encrypt dict flag or default flag (the default is true, + meaning that Metadata stream is encrypted as others) */ + if (ppdict_get_bool(encrypt, "EncryptMetadata", &encryptmd) && !encryptmd) + crypt->flags |= PPCRYPT_NO_METADATA; + + strkeylength = stmkeylength = 0; + /* streams filter */ + if ((name = ppdict_get_name(encrypt, "StmF")) != NULL && ppcrypt_type(crypt, name, &stmkeylength, &cryptflags)) + { + if (cryptflags & PPCRYPT_INFO_AES) + crypt->flags |= PPCRYPT_STREAM_AES; + else if (cryptflags & PPCRYPT_INFO_RC4) + crypt->flags |= PPCRYPT_STREAM_RC4; + if (cryptflags & PPCRYPT_INFO_NOMD) + crypt->flags |= PPCRYPT_NO_METADATA; + else if (cryptflags & PPCRYPT_INFO_MD) + crypt->flags &= ~PPCRYPT_NO_METADATA; + } /* else identity */ + /* strings filter */ + if ((name = ppdict_get_name(encrypt, "StrF")) != NULL && ppcrypt_type(crypt, name, &strkeylength, &cryptflags)) + { + if (cryptflags & PPCRYPT_INFO_AES) + crypt->flags |= PPCRYPT_STRING_AES; + else if (cryptflags & PPCRYPT_INFO_RC4) + crypt->flags |= PPCRYPT_STRING_RC4; + } /* else identity */ + + /* /Length of encrypt dict is irrelevant here, theoretically every crypt filter may have own length... It means that we should + actually keep a different file key for streams and strings. But it leads to nonsense, as /U and /O entries refers to a single + keylength, without a distinction for strings/streams. So we have to assume /Length is consistent. To expose the limitation: */ + if ((crypt->flags & PPCRYPT_STREAM) && (crypt->flags & PPCRYPT_STRING)) + if (strkeylength != stmkeylength) + return PPCRYPT_FAIL; + crypt->filekeylength = stmkeylength ? stmkeylength : strkeylength; + if ((crypt->flags & PPCRYPT_STREAM) || (crypt->flags & PPCRYPT_STRING)) + if (crypt->filekeylength == 0) + return PPCRYPT_FAIL; + break; + default: + return PPCRYPT_FAIL; + } + + /* setup passwords */ + if (userpass != NULL) + ppcrypt_set_user_password(crypt, userpass, userpasslength); + if (ownerpass != NULL) + ppcrypt_set_owner_password(crypt, ownerpass, ownerpasslength); + if ((crypt->flags & (PPCRYPT_USER_PASSWORD|PPCRYPT_OWNER_PASSWORD)) == 0) + return PPCRYPT_PASS; + + return ppcrypt_authenticate(crypt, userkey, userkey_e, ownerkey, ownerkey_e, id, perms); +} + +/* decrypting strings */ + +/* +Since strings are generally rare, but might occur in mass (name trees). We generate decryption key when needed. +All strings within the same reference are crypted with the same key. Both RC4 and AES algorithms expands +the crypt key in some way and the result of expansion is the same for the same crypt key. Instead of recreating +the ky for every string, we backup the initial decryption state. +*/ + +static void ppcrypt_strkey (ppcrypt *crypt, ppref *ref, int aes) +{ + if (crypt->cryptkeylength > 0) + { /* crypt key already generated, just reinitialize crypt states */ + if (aes) + { /* aes codecs that works on c-strings do not modify aes_state flags at all, so we actually don't need to revitalize the state, + we only rewrite an initialization vector, which is modified during crypt procedure */ + } + else + { /* rc4 crypt map is modified during crypt procedure, so here we reinitialize rc4 bytes map */ + rc4_map_restore(&crypt->rc4state, &crypt->rc4copy); + } + return; + } + + if (crypt->algorithm_variant < 5) + { + crypt->filekey[crypt->filekeylength + 0] = get_number_byte1(ref->number); + crypt->filekey[crypt->filekeylength + 1] = get_number_byte2(ref->number); + crypt->filekey[crypt->filekeylength + 2] = get_number_byte3(ref->number); + crypt->filekey[crypt->filekeylength + 3] = get_number_byte1(ref->version); + crypt->filekey[crypt->filekeylength + 4] = get_number_byte2(ref->version); + + if (aes) + { + crypt->filekey[crypt->filekeylength + 5] = 0x73; // s + crypt->filekey[crypt->filekeylength + 6] = 0x41; // A + crypt->filekey[crypt->filekeylength + 7] = 0x6C; // l + crypt->filekey[crypt->filekeylength + 8] = 0x54; // T + } + + md5_digest(crypt->filekey, crypt->filekeylength + (aes ? 9 : 5), crypt->cryptkey, MD5_BYTES); + crypt->cryptkeylength = crypt->filekeylength + 5 >= 16 ? 16 : crypt->filekeylength + 5; + } + else + { + memcpy(crypt->cryptkey, crypt->filekey, 32); + crypt->cryptkeylength = 32; + } + + if (aes) + { + aes_decode_initialize(&crypt->aesstate, &crypt->aeskeyblock, crypt->cryptkey, crypt->cryptkeylength, NULL); + aes_pdf_mode(&crypt->aesstate); + } + else + { + rc4_state_initialize(&crypt->rc4state, &crypt->rc4map, crypt->cryptkey, crypt->cryptkeylength); + rc4_map_save(&crypt->rc4state, &crypt->rc4copy); + } +} + +int ppstring_decrypt (ppcrypt *crypt, const void *input, size_t size, void *output, size_t *newsize) +{ + int aes, rc4; + aes = crypt->flags & PPCRYPT_STRING_AES; + rc4 = crypt->flags & PPCRYPT_STRING_RC4; + if (aes || rc4) + { + ppcrypt_strkey(crypt, crypt->ref, aes); + if (aes) + *newsize = aes_decode_state_data(&crypt->aesstate, input, size, output); + else // if (rc4) + *newsize = rc4_decode_state_data(&crypt->rc4state, input, size, output); + return 1; + } + return 0; // identity crypt +} + +/* decrypting streams */ + +/* +Streams are decrypted everytime when accessing the stream data. We need to be able to get or make +the key for decryption as long as the stream is alive. And to get the key we need the reference +number and version, plus document crypt info. First thought was to keep the reference to which +the stream belongs; stream->ref and accessing the crypt info stream->ref->xref->pdf->crypt. +It would be ok as long as absolutelly nothing happens with ref and crypt. At some point pplib +may drift into rewriting support, which would imply ref/xref/crypt/pdf structures modifications. +So I feel better with generating a crypt key for every stream in encrypted document, paying a cost +of md5 for all streams, not necessarily those actually read. + +Key generation is the same as for strings, but different for distinct encryption methods (rc4 vs aes). +Since streams and strings might theoretically be encrypted with different filters. No reason to cacche +decryption state here. +*/ + +ppstring * ppcrypt_stmkey (ppcrypt *crypt, ppref *ref, int aes, ppheap *heap) +{ + ppstring *cryptkeystring; + //if (crypt->cryptkeylength > 0) + // return; + + if (crypt->algorithm_variant < 5) + { + crypt->filekey[crypt->filekeylength + 0] = get_number_byte1(ref->number); + crypt->filekey[crypt->filekeylength + 1] = get_number_byte2(ref->number); + crypt->filekey[crypt->filekeylength + 2] = get_number_byte3(ref->number); + crypt->filekey[crypt->filekeylength + 3] = get_number_byte1(ref->version); + crypt->filekey[crypt->filekeylength + 4] = get_number_byte2(ref->version); + + if (aes) + { + crypt->filekey[crypt->filekeylength + 5] = 0x73; + crypt->filekey[crypt->filekeylength + 6] = 0x41; + crypt->filekey[crypt->filekeylength + 7] = 0x6C; + crypt->filekey[crypt->filekeylength + 8] = 0x54; + } + + md5_digest(crypt->filekey, crypt->filekeylength + (aes ? 9 : 5), crypt->cryptkey, MD5_BYTES); + crypt->cryptkeylength = crypt->filekeylength + 5 >= 16 ? 16 : crypt->filekeylength + 5; // how about 256bits AES?? + } + else + { // we could actually generate this string once, but.. aes itself is way more expensive that we can earn here + memcpy(crypt->cryptkey, crypt->filekey, 32); // just for the record + crypt->cryptkeylength = 32; + } + cryptkeystring = ppstring_internal(crypt->cryptkey, crypt->cryptkeylength, heap); + return ppstring_decoded(cryptkeystring); +} |