-
Notifications
You must be signed in to change notification settings - Fork 10.4k
Avoid password prompt on attachment-only encryption with no attachments #20140
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -31,6 +31,7 @@ import { | |
} from "./core_utils.js"; | ||
import { BaseStream } from "./base_stream.js"; | ||
import { CipherTransformFactory } from "./crypto.js"; | ||
import { NameTree } from "./name_number_tree.js"; | ||
|
||
class XRef { | ||
#firstXRefStmPos = null; | ||
|
@@ -118,22 +119,6 @@ class XRef { | |
} | ||
warn(`XRef.parse - Invalid "Encrypt" reference: "${ex}".`); | ||
} | ||
if (encrypt instanceof Dict) { | ||
const ids = trailerDict.get("ID"); | ||
const fileId = ids?.length ? ids[0] : ""; | ||
// The 'Encrypt' dictionary itself should not be encrypted, and by | ||
// setting `suppressEncryption` we can prevent an infinite loop inside | ||
// of `XRef_fetchUncompressed` if the dictionary contains indirect | ||
// objects (fixes issue7665.pdf). | ||
encrypt.suppressEncryption = true; | ||
this.encrypt = new CipherTransformFactory( | ||
encrypt, | ||
fileId, | ||
this.pdfManager.password | ||
); | ||
} | ||
|
||
// Get the root dictionary (catalog) object, and do some basic validation. | ||
let root; | ||
try { | ||
root = trailerDict.get("Root"); | ||
|
@@ -143,6 +128,43 @@ class XRef { | |
} | ||
warn(`XRef.parse - Invalid "Root" reference: "${ex}".`); | ||
} | ||
|
||
if (encrypt instanceof Dict) { | ||
// Check if only the file attachments are encrypted. | ||
if ( | ||
encrypt.get("CF")?.get("StdCF")?.get("AuthEvent")?.name === "EFOpen" | ||
) { | ||
let hasEncryptedAttachments = false; | ||
if (root instanceof Dict) { | ||
const names = root.get("Names"); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't understand why you're doing that. |
||
if (names instanceof Dict && names.has("EmbeddedFiles")) { | ||
const nameTree = new NameTree(names.getRaw("EmbeddedFiles"), this); | ||
const attachments = nameTree.getAll(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not sure about how much performance matters in this case, but we could have a |
||
if (attachments.size > 0) { | ||
hasEncryptedAttachments = true; | ||
} | ||
} | ||
} | ||
if (!hasEncryptedAttachments) { | ||
// If there are no encrypted attachments, encrypt dictionary is | ||
// not needed. | ||
encrypt = null; | ||
} | ||
} else { | ||
const ids = trailerDict.get("ID"); | ||
const fileId = ids?.length ? ids[0] : ""; | ||
// The 'Encrypt' dictionary itself should not be encrypted, and by | ||
// setting `suppressEncryption` we can prevent an infinite loop inside | ||
// of `XRef_fetchUncompressed` if the dictionary contains indirect | ||
// objects (fixes issue7665.pdf). | ||
encrypt.suppressEncryption = true; | ||
this.encrypt = new CipherTransformFactory( | ||
encrypt, | ||
fileId, | ||
this.pdfManager.password | ||
); | ||
} | ||
} | ||
if (root instanceof Dict) { | ||
try { | ||
const pages = root.get("Pages"); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As far as I can tell all this stuff is useless and only the
AuthEvent
entry should be checked:https://christianhaider.de/dokuwiki/lib/exe/fetch.php?media=pdf:pdf32000_2008.pdf#page=76&zoom=auto,-60,624
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
From what I understand, It looks like AuthEvent checks for when to check for encryption but doesn't really tell us what parts are encrypted, which is what we need to check if attachments are the only thing that's encrypted
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
According to the
AuthEvent
value we should only ask for the password when the user wants to access to the attachments (EFOpen
) or when the doc is open (DocOpen
).So if the value is
EFOpen
and there are no attachments, then the user won't be prompted.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I spent some time digging into this today, and based on what the spec says, I agree we can simplify things here. For PDFs with encrypted attachments, there appear to be three relevant cases:
/AuthEvent /EFOpen
, and includes attachments: (Issue [Bug]: File Attachments do not open when they are encrypted #20139)/AuthEvent /EFOpen
but no attachments, so we still need to check if any are actually present./AuthEvent
, but it uses/StmF /Identity
,/StrF /Identity
, and/EFF /StdCF
, this is a very specific case and not something we see in practice, I tried to generate a PDF that hits this edge-case, but I ended up with a PDF where the entries were/StmF /Identity
,/StrF /Identity
, and/EFF /StdCF
and there was no/AuthEvent
, but the content of the attachments was not actually encrypted.So now, I’ve removed the extra checks for /StmF, /StrF, and /EFF. We're only checking for /AuthEvent and whether the file actually has attachments.