[poppler] Branch 'xpdf303merge' - poppler/Decrypt.cc poppler/Decrypt.h poppler/PDFDoc.cc poppler/SecurityHandler.cc poppler/SecurityHandler.h poppler/Stream.h
Albert Astals Cid
aacid at kemper.freedesktop.org
Tue Aug 30 09:24:28 PDT 2011
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 <aacid at kde.org>
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
};
//------------------------------------------------------------------------
More information about the poppler
mailing list