poppler/Decrypt.cc | 488 +++++++++++++++++++++++++++++++++++++++------ poppler/Decrypt.h | 19 + poppler/PDFDoc.cc | 2 poppler/SecurityHandler.cc | 238 ++++++++++++--------- poppler/SecurityHandler.h | 7 poppler/Stream.h | 3 6 files changed, 595 insertions(+), 162 deletions(-)
New commits: commit 6364c50ffb4053cc30cecbefff7a3142cab8c50b Author: Albert Astals Cid <[email protected]> Date: Tue Aug 30 18:27:09 2011 +0200 xpdf303: Support for aes256 et all in Decrypt/SecurityHandler diff --git a/poppler/Decrypt.cc b/poppler/Decrypt.cc index 2373a66..4042eba 100644 --- a/poppler/Decrypt.cc +++ b/poppler/Decrypt.cc @@ -34,11 +34,13 @@ #include "Decrypt.h" #include "Error.h" -static void rc4InitKey(Guchar *key, int keyLen, Guchar *state); -static Guchar rc4DecryptByte(Guchar *state, Guchar *x, Guchar *y, Guchar c); static void aesKeyExpansion(DecryptAESState *s, Guchar *objKey, int objKeyLen); static void aesDecryptBlock(DecryptAESState *s, Guchar *in, GBool last); +static void aes256KeyExpansion(DecryptAES256State *s, + Guchar *objKey, int objKeyLen); +static void aes256DecryptBlock(DecryptAES256State *s, Guchar *in, GBool last); +static void sha256(Guchar *msg, int msgLen, Guchar *hash); static const Guchar passwordPad[32] = { 0x28, 0xbf, 0x4e, 0x5e, 0x4e, 0x75, 0x8a, 0x41, @@ -53,67 +55,139 @@ static const Guchar passwordPad[32] = { GBool Decrypt::makeFileKey(int encVersion, int encRevision, int keyLength, GooString *ownerKey, GooString *userKey, + GooString *ownerEnc, GooString *userEnc, int permissions, GooString *fileID, GooString *ownerPassword, GooString *userPassword, Guchar *fileKey, GBool encryptMetadata, GBool *ownerPasswordOk) { - Guchar test[32], test2[32]; + DecryptAES256State state; + Guchar test[127 + 56], test2[32]; GooString *userPassword2; Guchar fState[256]; Guchar tmpKey[16]; Guchar fx, fy; int len, i, j; - // try using the supplied owner password to generate the user password *ownerPasswordOk = gFalse; - if (ownerPassword) { - len = ownerPassword->getLength(); - if (len < 32) { + + if (encRevision == 5) { + + // check the owner password + if (ownerPassword) { + //~ this is supposed to convert the password to UTF-8 using "SASLprep" + len = ownerPassword->getLength(); + if (len > 127) { + len = 127; + } memcpy(test, ownerPassword->getCString(), len); - memcpy(test + len, passwordPad, 32 - len); - } else { - memcpy(test, ownerPassword->getCString(), 32); + memcpy(test + len, ownerKey->getCString() + 32, 8); + memcpy(test + len + 8, userKey->getCString(), 48); + sha256(test, len + 56, test); + if (!memcmp(test, ownerKey->getCString(), 32)) { + + // compute the file key from the owner password + memcpy(test, ownerPassword->getCString(), len); + memcpy(test + len, ownerKey->getCString() + 40, 8); + memcpy(test + len + 8, userKey->getCString(), 48); + sha256(test, len + 56, test); + aes256KeyExpansion(&state, test, 32); + for (i = 0; i < 16; ++i) { + state.cbc[i] = 0; + } + aes256DecryptBlock(&state, (Guchar *)ownerEnc->getCString(), gFalse); + memcpy(fileKey, state.buf, 16); + aes256DecryptBlock(&state, (Guchar *)ownerEnc->getCString() + 16, + gFalse); + memcpy(fileKey + 16, state.buf, 16); + + *ownerPasswordOk = gTrue; + return gTrue; + } } - md5(test, 32, test); - if (encRevision == 3) { - for (i = 0; i < 50; ++i) { - md5(test, keyLength, test); + + // check the user password + if (userPassword) { + //~ this is supposed to convert the password to UTF-8 using "SASLprep" + len = userPassword->getLength(); + if (len > 127) { + len = 127; + } + memcpy(test, userPassword->getCString(), len); + memcpy(test + len, userKey->getCString() + 32, 8); + sha256(test, len + 8, test); + if (!memcmp(test, userKey->getCString(), 32)) { + + // compute the file key from the user password + memcpy(test, userPassword->getCString(), len); + memcpy(test + len, userKey->getCString() + 40, 8); + sha256(test, len + 8, test); + aes256KeyExpansion(&state, test, 32); + for (i = 0; i < 16; ++i) { + state.cbc[i] = 0; + } + aes256DecryptBlock(&state, (Guchar *)userEnc->getCString(), gFalse); + memcpy(fileKey, state.buf, 16); + aes256DecryptBlock(&state, (Guchar *)userEnc->getCString() + 16, + gFalse); + memcpy(fileKey + 16, state.buf, 16); + + return gTrue; } } - if (encRevision == 2) { - rc4InitKey(test, keyLength, fState); - fx = fy = 0; - for (i = 0; i < 32; ++i) { - test2[i] = rc4DecryptByte(fState, &fx, &fy, ownerKey->getChar(i)); + + return gFalse; + } else { + + // try using the supplied owner password to generate the user password + if (ownerPassword) { + len = ownerPassword->getLength(); + if (len < 32) { + memcpy(test, ownerPassword->getCString(), len); + memcpy(test + len, passwordPad, 32 - len); + } else { + memcpy(test, ownerPassword->getCString(), 32); } - } else { - memcpy(test2, ownerKey->getCString(), 32); - for (i = 19; i >= 0; --i) { - for (j = 0; j < keyLength; ++j) { - tmpKey[j] = test[j] ^ i; + md5(test, 32, test); + if (encRevision == 3) { + for (i = 0; i < 50; ++i) { + md5(test, keyLength, test); } - rc4InitKey(tmpKey, keyLength, fState); + } + if (encRevision == 2) { + rc4InitKey(test, keyLength, fState); fx = fy = 0; - for (j = 0; j < 32; ++j) { - test2[j] = rc4DecryptByte(fState, &fx, &fy, test2[j]); + for (i = 0; i < 32; ++i) { + test2[i] = rc4DecryptByte(fState, &fx, &fy, ownerKey->getChar(i)); + } + } else { + memcpy(test2, ownerKey->getCString(), 32); + for (i = 19; i >= 0; --i) { + for (j = 0; j < keyLength; ++j) { + tmpKey[j] = test[j] ^ i; + } + rc4InitKey(tmpKey, keyLength, fState); + fx = fy = 0; + for (j = 0; j < 32; ++j) { + test2[j] = rc4DecryptByte(fState, &fx, &fy, test2[j]); + } } } - } - userPassword2 = new GooString((char *)test2, 32); - if (makeFileKey2(encVersion, encRevision, keyLength, ownerKey, userKey, - permissions, fileID, userPassword2, fileKey, - encryptMetadata)) { - *ownerPasswordOk = gTrue; + userPassword2 = new GooString((char *)test2, 32); + if (makeFileKey2(encVersion, encRevision, keyLength, ownerKey, userKey, + permissions, fileID, userPassword2, fileKey, + encryptMetadata)) { + *ownerPasswordOk = gTrue; + delete userPassword2; + return gTrue; + } delete userPassword2; - return gTrue; } - delete userPassword2; - } - // try using the supplied user password - return makeFileKey2(encVersion, encRevision, keyLength, ownerKey, userKey, - permissions, fileID, userPassword, fileKey, - encryptMetadata); + // try using the supplied user password + return makeFileKey2(encVersion, encRevision, keyLength, ownerKey, userKey, + permissions, fileID, userPassword, fileKey, + encryptMetadata); + } } GBool Decrypt::makeFileKey2(int encVersion, int encRevision, int keyLength, @@ -203,7 +277,7 @@ DecryptStream::DecryptStream(Stream *strA, Guchar *fileKey, int objNum, int objGen): FilterStream(strA) { - int n, i; + int i; algo = algoA; @@ -211,23 +285,36 @@ DecryptStream::DecryptStream(Stream *strA, Guchar *fileKey, for (i = 0; i < keyLength; ++i) { objKey[i] = fileKey[i]; } - objKey[keyLength] = objNum & 0xff; - objKey[keyLength + 1] = (objNum >> 8) & 0xff; - objKey[keyLength + 2] = (objNum >> 16) & 0xff; - objKey[keyLength + 3] = objGen & 0xff; - objKey[keyLength + 4] = (objGen >> 8) & 0xff; - if (algo == cryptAES) { + switch (algo) { + case cryptRC4: + objKey[keyLength] = objNum & 0xff; + objKey[keyLength + 1] = (objNum >> 8) & 0xff; + objKey[keyLength + 2] = (objNum >> 16) & 0xff; + objKey[keyLength + 3] = objGen & 0xff; + objKey[keyLength + 4] = (objGen >> 8) & 0xff; + md5(objKey, keyLength + 5, objKey); + if ((objKeyLength = keyLength + 5) > 16) { + objKeyLength = 16; + } + break; + case cryptAES: + objKey[keyLength] = objNum & 0xff; + objKey[keyLength + 1] = (objNum >> 8) & 0xff; + objKey[keyLength + 2] = (objNum >> 16) & 0xff; + objKey[keyLength + 3] = objGen & 0xff; + objKey[keyLength + 4] = (objGen >> 8) & 0xff; objKey[keyLength + 5] = 0x73; // 's' objKey[keyLength + 6] = 0x41; // 'A' objKey[keyLength + 7] = 0x6c; // 'l' objKey[keyLength + 8] = 0x54; // 'T' - n = keyLength + 9; - } else { - n = keyLength + 5; - } - Decrypt::md5(objKey, n, objKey); - if ((objKeyLength = keyLength + 5) > 16) { - objKeyLength = 16; + md5(objKey, keyLength + 9, objKey); + if ((objKeyLength = keyLength + 5) > 16) { + objKeyLength = 16; + } + break; + case cryptAES256: + objKeyLength = keyLength; + break; } charactersRead = 0; @@ -255,6 +342,13 @@ void DecryptStream::reset() { } state.aes.bufIdx = 16; break; + case cryptAES256: + aes256KeyExpansion(&state.aes256, objKey, objKeyLength); + for (i = 0; i < 16; ++i) { + state.aes256.cbc[i] = str->getChar(); + } + state.aes256.bufIdx = 16; + break; } } @@ -295,6 +389,22 @@ int DecryptStream::getChar() { c = state.aes.buf[state.aes.bufIdx++]; } break; + case cryptAES256: + if (state.aes256.bufIdx == 16) { + for (i = 0; i < 16; ++i) { + if ((c = str->getChar()) == EOF) { + return EOF; + } + in[i] = (Guchar)c; + } + aes256DecryptBlock(&state.aes256, in, str->lookChar() == EOF); + } + if (state.aes256.bufIdx == 16) { + c = EOF; + } else { + c = state.aes256.buf[state.aes256.bufIdx++]; + } + break; } if (c != EOF) charactersRead++; @@ -333,6 +443,22 @@ int DecryptStream::lookChar() { c = state.aes.buf[state.aes.bufIdx]; } break; + case cryptAES256: + if (state.aes256.bufIdx == 16) { + for (i = 0; i < 16; ++i) { + if ((c = str->getChar()) == EOF) { + return EOF; + } + in[i] = c; + } + aes256DecryptBlock(&state.aes256, in, str->lookChar() == EOF); + } + if (state.aes256.bufIdx == 16) { + c = EOF; + } else { + c = state.aes256.buf[state.aes256.bufIdx]; + } + break; } return c; } @@ -345,7 +471,7 @@ GBool DecryptStream::isBinary(GBool last) { // RC4-compatible decryption //------------------------------------------------------------------------ -static void rc4InitKey(Guchar *key, int keyLen, Guchar *state) { +void rc4InitKey(Guchar *key, int keyLen, Guchar *state) { Guchar index1, index2; Guchar t; int i; @@ -366,7 +492,7 @@ static void rc4InitKey(Guchar *key, int keyLen, Guchar *state) { } } -static Guchar rc4DecryptByte(Guchar *state, Guchar *x, Guchar *y, Guchar c) { +Guchar rc4DecryptByte(Guchar *state, Guchar *x, Guchar *y, Guchar c) { Guchar x1, y1, tx, ty; x1 = *x = (*x + 1) % 256; @@ -626,6 +752,92 @@ static void aesDecryptBlock(DecryptAESState *s, Guchar *in, GBool last) { s->bufIdx = 0; if (last) { n = s->buf[15]; + if (n < 1 || n > 16) { // this should never happen + n = 16; + } + for (i = 15; i >= n; --i) { + s->buf[i] = s->buf[i-n]; + } + s->bufIdx = n; + } +} + +//------------------------------------------------------------------------ +// AES-256 decryption +//------------------------------------------------------------------------ + +static void aes256KeyExpansion(DecryptAES256State *s, + Guchar *objKey, int objKeyLen) { + Guint temp; + int i, round; + + //~ this assumes objKeyLen == 32 + + for (i = 0; i < 8; ++i) { + s->w[i] = (objKey[4*i] << 24) + (objKey[4*i+1] << 16) + + (objKey[4*i+2] << 8) + objKey[4*i+3]; + } + for (i = 8; i < 60; ++i) { + temp = s->w[i-1]; + if ((i & 7) == 0) { + temp = subWord(rotWord(temp)) ^ rcon[i/8]; + } else if ((i & 7) == 4) { + temp = subWord(temp); + } + s->w[i] = s->w[i-8] ^ temp; + } + for (round = 1; round <= 13; ++round) { + invMixColumnsW(&s->w[round * 4]); + } +} + +static void aes256DecryptBlock(DecryptAES256State *s, Guchar *in, GBool last) { + int c, round, n, i; + + // initial state + for (c = 0; c < 4; ++c) { + s->state[c] = in[4*c]; + s->state[4+c] = in[4*c+1]; + s->state[8+c] = in[4*c+2]; + s->state[12+c] = in[4*c+3]; + } + + // round 0 + addRoundKey(s->state, &s->w[14 * 4]); + + // rounds 13-1 + for (round = 13; round >= 1; --round) { + invSubBytes(s->state); + invShiftRows(s->state); + invMixColumns(s->state); + addRoundKey(s->state, &s->w[round * 4]); + } + + // round 14 + invSubBytes(s->state); + invShiftRows(s->state); + addRoundKey(s->state, &s->w[0]); + + // CBC + for (c = 0; c < 4; ++c) { + s->buf[4*c] = s->state[c] ^ s->cbc[4*c]; + s->buf[4*c+1] = s->state[4+c] ^ s->cbc[4*c+1]; + s->buf[4*c+2] = s->state[8+c] ^ s->cbc[4*c+2]; + s->buf[4*c+3] = s->state[12+c] ^ s->cbc[4*c+3]; + } + + // save the input block for the next CBC + for (i = 0; i < 16; ++i) { + s->cbc[i] = in[i]; + } + + // remove padding + s->bufIdx = 0; + if (last) { + n = s->buf[15]; + if (n < 1 || n > 16) { // this should never happen + n = 16; + } for (i = 15; i >= n; --i) { s->buf[i] = s->buf[i-n]; } @@ -668,12 +880,17 @@ static inline Gulong md5Round4(Gulong a, Gulong b, Gulong c, Gulong d, return b + rotateLeft((a + (c ^ (b | ~d)) + Xk + Ti), s); } -void Decrypt::md5(Guchar *msg, int msgLen, Guchar *digest) { +void md5(Guchar *msg, int msgLen, Guchar *digest) { Gulong x[16]; Gulong a, b, c, d, aa, bb, cc, dd; int n64; int i, j, k; + // sanity check + if (msgLen < 0) { + return; + } + // compute number of 64-byte blocks // (length + pad byte (0x80) + 8 bytes for length) n64 = (msgLen + 1 + 8 + 63) / 64; @@ -809,3 +1026,160 @@ void Decrypt::md5(Guchar *msg, int msgLen, Guchar *digest) { digest[14] = (Guchar)((d >>= 8) & 0xff); digest[15] = (Guchar)((d >>= 8) & 0xff); } + +//------------------------------------------------------------------------ +// SHA-256 hash +//------------------------------------------------------------------------ + +static Guint sha256K[64] = { + 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, + 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, + 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, + 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, + 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, + 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, + 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, + 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, + 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, + 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, + 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, + 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, + 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, + 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, + 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, + 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2 +}; + +static inline Guint rotr(Guint x, Guint n) { + return (x >> n) | (x << (32 - n)); +} + +static inline Guint sha256Ch(Guint x, Guint y, Guint z) { + return (x & y) ^ (~x & z); +} + +static inline Guint sha256Maj(Guint x, Guint y, Guint z) { + return (x & y) ^ (x & z) ^ (y & z); +} + +static inline Guint sha256Sigma0(Guint x) { + return rotr(x, 2) ^ rotr(x, 13) ^ rotr(x, 22); +} + +static inline Guint sha256Sigma1(Guint x) { + return rotr(x, 6) ^ rotr(x, 11) ^ rotr(x, 25); +} + +static inline Guint sha256sigma0(Guint x) { + return rotr(x, 7) ^ rotr(x, 18) ^ (x >> 3); +} + +static inline Guint sha256sigma1(Guint x) { + return rotr(x, 17) ^ rotr(x, 19) ^ (x >> 10); +} + +void sha256HashBlock(Guchar *blk, Guint *H) { + Guint W[64]; + Guint a, b, c, d, e, f, g, h; + Guint T1, T2; + Guint t; + + // 1. prepare the message schedule + for (t = 0; t < 16; ++t) { + W[t] = (blk[t*4] << 24) | + (blk[t*4 + 1] << 16) | + (blk[t*4 + 2] << 8) | + blk[t*4 + 3]; + } + for (t = 16; t < 64; ++t) { + W[t] = sha256sigma1(W[t-2]) + W[t-7] + sha256sigma0(W[t-15]) + W[t-16]; + } + + // 2. initialize the eight working variables + a = H[0]; + b = H[1]; + c = H[2]; + d = H[3]; + e = H[4]; + f = H[5]; + g = H[6]; + h = H[7]; + + // 3. + for (t = 0; t < 64; ++t) { + T1 = h + sha256Sigma1(e) + sha256Ch(e,f,g) + sha256K[t] + W[t]; + T2 = sha256Sigma0(a) + sha256Maj(a,b,c); + h = g; + g = f; + f = e; + e = d + T1; + d = c; + c = b; + b = a; + a = T1 + T2; + } + + // 4. compute the intermediate hash value + H[0] += a; + H[1] += b; + H[2] += c; + H[3] += d; + H[4] += e; + H[5] += f; + H[6] += g; + H[7] += h; +} + +static void sha256(Guchar *msg, int msgLen, Guchar *hash) { + Guchar blk[64]; + Guint H[8]; + int blkLen, i; + + H[0] = 0x6a09e667; + H[1] = 0xbb67ae85; + H[2] = 0x3c6ef372; + H[3] = 0xa54ff53a; + H[4] = 0x510e527f; + H[5] = 0x9b05688c; + H[6] = 0x1f83d9ab; + H[7] = 0x5be0cd19; + + blkLen = 0; + for (i = 0; i + 64 <= msgLen; i += 64) { + sha256HashBlock(msg + i, H); + } + blkLen = msgLen - i; + if (blkLen > 0) { + memcpy(blk, msg + i, blkLen); + } + + // pad the message + blk[blkLen++] = 0x80; + if (blkLen > 56) { + while (blkLen < 64) { + blk[blkLen++] = 0; + } + sha256HashBlock(blk, H); + blkLen = 0; + } + while (blkLen < 56) { + blk[blkLen++] = 0; + } + blk[56] = 0; + blk[57] = 0; + blk[58] = 0; + blk[59] = 0; + blk[60] = (Guchar)(msgLen >> 21); + blk[61] = (Guchar)(msgLen >> 13); + blk[62] = (Guchar)(msgLen >> 5); + blk[63] = (Guchar)(msgLen << 3); + sha256HashBlock(blk, H); + + // copy the output into the buffer (convert words to bytes) + for (i = 0; i < 8; ++i) { + hash[i*4] = (Guchar)(H[i] >> 24); + hash[i*4 + 1] = (Guchar)(H[i] >> 16); + hash[i*4 + 2] = (Guchar)(H[i] >> 8); + hash[i*4 + 3] = (Guchar)H[i]; + } +} diff --git a/poppler/Decrypt.h b/poppler/Decrypt.h index feb7014..d947f41 100644 --- a/poppler/Decrypt.h +++ b/poppler/Decrypt.h @@ -39,7 +39,6 @@ class Decrypt { public: - static void md5(Guchar *msg, int msgLen, Guchar *digest); // Generate a file key. The <fileKey> buffer must have space for at // least 16 bytes. Checks <ownerPassword> and then <userPassword> @@ -48,6 +47,7 @@ public: // may be NULL, which is treated as an empty string. static GBool makeFileKey(int encVersion, int encRevision, int keyLength, GooString *ownerKey, GooString *userKey, + GooString *ownerEnc, GooString *userEnc, int permissions, GooString *fileID, GooString *ownerPassword, GooString *userPassword, Guchar *fileKey, GBool encryptMetadata, @@ -80,6 +80,14 @@ struct DecryptAESState { int bufIdx; }; +struct DecryptAES256State { + Guint w[60]; + Guchar state[16]; + Guchar cbc[16]; + Guchar buf[16]; + int bufIdx; +}; + class DecryptStream: public FilterStream { public: @@ -99,13 +107,20 @@ private: CryptAlgorithm algo; int objKeyLength; - Guchar objKey[16 + 9]; + Guchar objKey[32]; int charactersRead; // so that getPos() can be correct union { DecryptRC4State rc4; DecryptAESState aes; + DecryptAES256State aes256; } state; }; + +//------------------------------------------------------------------------ + +extern void rc4InitKey(Guchar *key, int keyLen, Guchar *state); +extern Guchar rc4DecryptByte(Guchar *state, Guchar *x, Guchar *y, Guchar c); +extern void md5(Guchar *msg, int msgLen, Guchar *digest); #endif diff --git a/poppler/PDFDoc.cc b/poppler/PDFDoc.cc index 3092cea..51bb22b 100644 --- a/poppler/PDFDoc.cc +++ b/poppler/PDFDoc.cc @@ -1095,7 +1095,7 @@ void PDFDoc::writeTrailer(Guint uxrefOffset, int uxrefSize, //calculate md5 digest Guchar digest[16]; - Decrypt::md5((Guchar*)message.getCString(), message.getLength(), digest); + md5((Guchar*)message.getCString(), message.getLength(), digest); obj1.initString(new GooString((const char*)digest, 16)); //create ID array diff --git a/poppler/SecurityHandler.cc b/poppler/SecurityHandler.cc index 630c753..1e847c9 100644 --- a/poppler/SecurityHandler.cc +++ b/poppler/SecurityHandler.cc @@ -31,11 +31,6 @@ #include "Decrypt.h" #include "Error.h" #include "GlobalParams.h" -#if HAVE_XPDFCORE -# include "XPDFCore.h" -#elif HAVE_WINPDFCORE -# include "WinPDFCore.h" -#endif #ifdef ENABLE_PLUGINS # include "XpdfPluginAPI.h" #endif @@ -63,14 +58,15 @@ SecurityHandler *SecurityHandler::make(PDFDoc *docA, Object *encryptDictA) { secHdlr = new ExternalSecurityHandler(docA, encryptDictA, xsh); } else { #endif - error(-1, "Couldn't find the '%s' security handler", + error(/*errSyntaxError, */-1, "Couldn't find the '%s' security handler", filterObj.getName()); secHdlr = NULL; #ifdef ENABLE_PLUGINS } #endif } else { - error(-1, "Missing or invalid 'Filter' entry in encryption dictionary"); + error(/*errSyntaxError, */-1, + "Missing or invalid 'Filter' entry in encryption dictionary"); secHdlr = NULL; } filterObj.free(); @@ -109,7 +105,7 @@ GBool SecurityHandler::checkEncryption(GooString *ownerPassword, } } if (!ok) { - error(-1, "Incorrect password"); + error(/*errCommandLine, */-1, "Incorrect password"); } return ok; } @@ -144,8 +140,8 @@ StandardSecurityHandler::StandardSecurityHandler(PDFDoc *docA, SecurityHandler(docA) { Object versionObj, revisionObj, lengthObj; - Object ownerKeyObj, userKeyObj, permObj, fileIDObj; - Object fileIDObj1; + Object ownerKeyObj, userKeyObj, ownerEncObj, userEncObj; + Object permObj, fileIDObj, fileIDObj1; Object cryptFiltersObj, streamFilterObj, stringFilterObj; Object cryptFilterObj, cfmObj, cfLengthObj; Object encryptMetadataObj; @@ -154,12 +150,17 @@ StandardSecurityHandler::StandardSecurityHandler(PDFDoc *docA, fileID = NULL; ownerKey = NULL; userKey = NULL; + ownerEnc = NULL; + userEnc = NULL; + fileKeyLength = 0; encryptDictA->dictLookup("V", &versionObj); encryptDictA->dictLookup("R", &revisionObj); encryptDictA->dictLookup("Length", &lengthObj); encryptDictA->dictLookup("O", &ownerKeyObj); encryptDictA->dictLookup("U", &userKeyObj); + encryptDictA->dictLookup("OE", &ownerEncObj); + encryptDictA->dictLookup("UE", &userEncObj); encryptDictA->dictLookup("P", &permObj); if (permObj.isUint()) { unsigned int permUint = permObj.getUint(); @@ -170,91 +171,137 @@ StandardSecurityHandler::StandardSecurityHandler(PDFDoc *docA, doc->getXRef()->getTrailerDict()->dictLookup("ID", &fileIDObj); if (versionObj.isInt() && revisionObj.isInt() && - ownerKeyObj.isString() && ownerKeyObj.getString()->getLength() == 32 && - userKeyObj.isString() && userKeyObj.getString()->getLength() == 32 && - permObj.isInt()) { + permObj.isInt() && + ownerKeyObj.isString() && + userKeyObj.isString()) { encVersion = versionObj.getInt(); encRevision = revisionObj.getInt(); - encAlgorithm = cryptRC4; - // revision 2 forces a 40-bit key - some buggy PDF generators - // set the Length value incorrectly - if (encRevision == 2 || !lengthObj.isInt()) { - fileKeyLength = 5; - } else { - fileKeyLength = lengthObj.getInt() / 8; - } - encryptMetadata = gTrue; - //~ this currently only handles a subset of crypt filter functionality - if (encVersion == 4 && encRevision == 4) { - encryptDictA->dictLookup("CF", &cryptFiltersObj); - encryptDictA->dictLookup("StmF", &streamFilterObj); - encryptDictA->dictLookup("StrF", &stringFilterObj); - if (cryptFiltersObj.isDict() && - streamFilterObj.isName() && - stringFilterObj.isName() && - !strcmp(streamFilterObj.getName(), stringFilterObj.getName())) { - if (cryptFiltersObj.dictLookup(streamFilterObj.getName(), - &cryptFilterObj)->isDict()) { - cryptFilterObj.dictLookup("CFM", &cfmObj); - if (cfmObj.isName("V2")) { - encVersion = 2; - encRevision = 3; - if (cryptFilterObj.dictLookup("Length", &cfLengthObj)->isInt()) { - //~ according to the spec, this should be cfLengthObj / 8 - fileKeyLength = cfLengthObj.getInt(); - } - cfLengthObj.free(); - } else if (cfmObj.isName("AESV2")) { - encVersion = 2; - encRevision = 3; - encAlgorithm = cryptAES; - if (cryptFilterObj.dictLookup("Length", &cfLengthObj)->isInt()) { - //~ according to the spec, this should be cfLengthObj / 8 - fileKeyLength = cfLengthObj.getInt(); + if ((encRevision <= 4 && + ownerKeyObj.getString()->getLength() == 32 && + userKeyObj.getString()->getLength() == 32) || + (encRevision == 5 && + // the spec says 48 bytes, but Acrobat pads them out longer + ownerKeyObj.getString()->getLength() >= 48 && + userKeyObj.getString()->getLength() >= 48 && + ownerEncObj.isString() && + ownerEncObj.getString()->getLength() == 32 && + userEncObj.isString() && + userEncObj.getString()->getLength() == 32)) { + encAlgorithm = cryptRC4; + // revision 2 forces a 40-bit key - some buggy PDF generators + // set the Length value incorrectly + if (encRevision == 2 || !lengthObj.isInt()) { + fileKeyLength = 5; + } else { + fileKeyLength = lengthObj.getInt() / 8; + } + encryptMetadata = gTrue; + //~ this currently only handles a subset of crypt filter functionality + //~ (in particular, it ignores the EFF entry in encryptDictA, and + //~ doesn't handle the case where StmF, StrF, and EFF are not all the + //~ same) + if ((encVersion == 4 || encVersion == 5) && + (encRevision == 4 || encRevision == 5)) { + encryptDictA->dictLookup("CF", &cryptFiltersObj); + encryptDictA->dictLookup("StmF", &streamFilterObj); + encryptDictA->dictLookup("StrF", &stringFilterObj); + if (cryptFiltersObj.isDict() && + streamFilterObj.isName() && + stringFilterObj.isName() && + !strcmp(streamFilterObj.getName(), stringFilterObj.getName())) { + if (!strcmp(streamFilterObj.getName(), "Identity")) { + // no encryption on streams or strings + encVersion = encRevision = -1; + } else { + if (cryptFiltersObj.dictLookup(streamFilterObj.getName(), + &cryptFilterObj)->isDict()) { + cryptFilterObj.dictLookup("CFM", &cfmObj); + if (cfmObj.isName("V2")) { + encVersion = 2; + encRevision = 3; + if (cryptFilterObj.dictLookup("Length", + &cfLengthObj)->isInt()) { + //~ according to the spec, this should be cfLengthObj / 8 + fileKeyLength = cfLengthObj.getInt(); + } + cfLengthObj.free(); + } else if (cfmObj.isName("AESV2")) { + encVersion = 2; + encRevision = 3; + encAlgorithm = cryptAES; + if (cryptFilterObj.dictLookup("Length", + &cfLengthObj)->isInt()) { + //~ according to the spec, this should be cfLengthObj / 8 + fileKeyLength = cfLengthObj.getInt(); + } + cfLengthObj.free(); + } else if (cfmObj.isName("AESV3")) { + encVersion = 5; + encRevision = 5; + encAlgorithm = cryptAES256; + if (cryptFilterObj.dictLookup("Length", + &cfLengthObj)->isInt()) { + //~ according to the spec, this should be cfLengthObj / 8 + fileKeyLength = cfLengthObj.getInt(); + } + cfLengthObj.free(); + } + cfmObj.free(); } - cfLengthObj.free(); + cryptFilterObj.free(); } - cfmObj.free(); } - cryptFilterObj.free(); - } - stringFilterObj.free(); - streamFilterObj.free(); - cryptFiltersObj.free(); - if (encryptDictA->dictLookup("EncryptMetadata", - &encryptMetadataObj)->isBool()) { - encryptMetadata = encryptMetadataObj.getBool(); + stringFilterObj.free(); + streamFilterObj.free(); + cryptFiltersObj.free(); + if (encryptDictA->dictLookup("EncryptMetadata", + &encryptMetadataObj)->isBool()) { + encryptMetadata = encryptMetadataObj.getBool(); + } + encryptMetadataObj.free(); } - encryptMetadataObj.free(); - } - permFlags = permObj.getInt(); - ownerKey = ownerKeyObj.getString()->copy(); - userKey = userKeyObj.getString()->copy(); - if (encVersion >= 1 && encVersion <= 2 && - encRevision >= 2 && encRevision <= 3) { - if (fileIDObj.isArray()) { - if (fileIDObj.arrayGet(0, &fileIDObj1)->isString()) { - fileID = fileIDObj1.getString()->copy(); + permFlags = permObj.getInt(); + ownerKey = ownerKeyObj.getString()->copy(); + userKey = userKeyObj.getString()->copy(); + if (encVersion >= 1 && encVersion <= 2 && + encRevision >= 2 && encRevision <= 3) { + if (fileIDObj.isArray()) { + if (fileIDObj.arrayGet(0, &fileIDObj1)->isString()) { + fileID = fileIDObj1.getString()->copy(); + } else { + fileID = new GooString(); + } + fileIDObj1.free(); } else { fileID = new GooString(); } - fileIDObj1.free(); - } else { - fileID = new GooString(); + if (fileKeyLength > 16 || fileKeyLength < 0) { + fileKeyLength = 16; + } + ok = gTrue; + } else if (encVersion == 5 && encRevision == 5) { + fileID = new GooString(); // unused for V=R=5 + ownerEnc = ownerEncObj.getString()->copy(); + userEnc = userEncObj.getString()->copy(); + if (fileKeyLength > 32 || fileKeyLength < 0) { + fileKeyLength = 32; + } + ok = gTrue; + } else if (!(encVersion == -1 && encRevision == -1)) { + error(/*errUnimplemented, */-1, + "Unsupported version/revision (%d/%d) of Standard security handler", + encVersion, encRevision); } - ok = gTrue; } else { - error(-1, "Unsupported version/revision (%d/%d) of Standard security handler", - encVersion, encRevision); + error(/*errSyntaxError, */-1, "Invalid encryption key length"); } } else { - error(-1, "Weird encryption info"); - } - if (fileKeyLength > 16) { - fileKeyLength = 16; + error(/*errSyntaxError, */-1, "Weird encryption info"); } fileIDObj.free(); permObj.free(); + userEncObj.free(); + ownerEncObj.free(); userKeyObj.free(); ownerKeyObj.free(); lengthObj.free(); @@ -272,6 +319,16 @@ StandardSecurityHandler::~StandardSecurityHandler() { if (userKey) { delete userKey; } + if (ownerEnc) { + delete ownerEnc; + } + if (userEnc) { + delete userEnc; + } +} + +GBool StandardSecurityHandler::isUnencrypted() { + return encVersion == -1 && encRevision == -1; } void *StandardSecurityHandler::makeAuthData(GooString *ownerPassword, @@ -283,27 +340,7 @@ void *StandardSecurityHandler::makeAuthData(GooString *ownerPassword, } void *StandardSecurityHandler::getAuthData() { -#if HAVE_XPDFCORE - XPDFCore *core; - GooString *password; - - if (!(core = (XPDFCore *)doc->getGUIData()) || - !(password = core->getPassword())) { - return NULL; - } - return new StandardAuthData(password, password->copy()); -#elif HAVE_WINPDFCORE - WinPDFCore *core; - GooString *password; - - if (!(core = (WinPDFCore *)doc->getGUIData()) || - !(password = core->getPassword())) { - return NULL; - } - return new StandardAuthData(password, password->copy()); -#else return NULL; -#endif } void StandardSecurityHandler::freeAuthData(void *authData) { @@ -324,7 +361,8 @@ GBool StandardSecurityHandler::authorize(void *authData) { userPassword = NULL; } if (!Decrypt::makeFileKey(encVersion, encRevision, fileKeyLength, - ownerKey, userKey, permFlags, fileID, + ownerKey, userKey, ownerEnc, userEnc, + permFlags, fileID, ownerPassword, userPassword, fileKey, encryptMetadata, &ownerPasswordOk)) { return gFalse; diff --git a/poppler/SecurityHandler.h b/poppler/SecurityHandler.h index 5278a6c..57cb2d2 100644 --- a/poppler/SecurityHandler.h +++ b/poppler/SecurityHandler.h @@ -34,6 +34,9 @@ public: SecurityHandler(PDFDoc *docA); virtual ~SecurityHandler(); + // Returns true if the file is actually unencrypted. + virtual GBool isUnencrypted() { return gFalse; } + // Check the document's encryption. If the document is encrypted, // this will first try <ownerPassword> and <userPassword> (in // "batch" mode), and if those fail, it will attempt to request a @@ -92,6 +95,7 @@ public: StandardSecurityHandler(PDFDoc *docA, Object *encryptDictA); virtual ~StandardSecurityHandler(); + virtual GBool isUnencrypted(); virtual void *makeAuthData(GooString *ownerPassword, GooString *userPassword); virtual void *getAuthData(); @@ -109,7 +113,7 @@ private: int permFlags; GBool ownerPasswordOk; - Guchar fileKey[16]; + Guchar fileKey[32]; int fileKeyLength; int encVersion; int encRevision; @@ -117,6 +121,7 @@ private: CryptAlgorithm encAlgorithm; GooString *ownerKey, *userKey; + GooString *ownerEnc, *userEnc; GooString *fileID; GBool ok; }; diff --git a/poppler/Stream.h b/poppler/Stream.h index 6ede153..138402d 100644 --- a/poppler/Stream.h +++ b/poppler/Stream.h @@ -71,7 +71,8 @@ enum StreamColorSpaceMode { // include file dependency loops. enum CryptAlgorithm { cryptRC4, - cryptAES + cryptAES, + cryptAES256 }; //------------------------------------------------------------------------ _______________________________________________ poppler mailing list [email protected] http://lists.freedesktop.org/mailman/listinfo/poppler
