Fix password encoding when generating PDFs. Per https://crbug.com/pdfium/1194, PDFium accepts passwords in either Latin1 or UTF8 encoding, and will re-encode, if necessary, to correctly decrypt PDFs. When the encrypted PDFs are copied and saved, the copy needs to be encrypted with the password with the proper encoding. Do so by remembering the re-encode status in CPDF_SecurityHandler. Then use that knowledge in GetEncodedPassword() to encode passwords properly. Re-enable CPDFSecurityHandlerEmbedderTest code that was disabled due to this issue, and add new TODOs for test cases that still do not work due to other issues. Bug: pdfium:1440,pdfium:1441 Change-Id: I4fe27403db06a1a5dcb90d307fe00af181a1ad22 Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/63831 Commit-Queue: Lei Zhang <thestig@chromium.org> Reviewed-by: Tom Sepez <tsepez@chromium.org>
diff --git a/core/fpdfapi/edit/cpdf_creator.cpp b/core/fpdfapi/edit/cpdf_creator.cpp index 71ef379..6c99ff4 100644 --- a/core/fpdfapi/edit/cpdf_creator.cpp +++ b/core/fpdfapi/edit/cpdf_creator.cpp
@@ -623,7 +623,7 @@ m_pEncryptDict = m_pNewEncryptDict; m_pSecurityHandler = pdfium::MakeRetain<CPDF_SecurityHandler>(); m_pSecurityHandler->OnCreate(m_pNewEncryptDict.Get(), m_pIDArray.Get(), - m_pParser->GetPassword()); + m_pParser->GetEncodedPassword()); m_bSecurityChanged = true; } }
diff --git a/core/fpdfapi/parser/cpdf_parser.cpp b/core/fpdfapi/parser/cpdf_parser.cpp index 8cfb6fc..3e2819b 100644 --- a/core/fpdfapi/parser/cpdf_parser.cpp +++ b/core/fpdfapi/parser/cpdf_parser.cpp
@@ -820,6 +820,10 @@ return nullptr; } +ByteString CPDF_Parser::GetEncodedPassword() const { + return GetSecurityHandler()->GetEncodedPassword(GetPassword().AsStringView()); +} + const CPDF_Dictionary* CPDF_Parser::GetTrailer() const { return m_CrossRefTable->trailer(); }
diff --git a/core/fpdfapi/parser/cpdf_parser.h b/core/fpdfapi/parser/cpdf_parser.h index 8259f08..d44244a 100644 --- a/core/fpdfapi/parser/cpdf_parser.h +++ b/core/fpdfapi/parser/cpdf_parser.h
@@ -67,6 +67,10 @@ void SetPassword(const char* password) { m_Password = password; } ByteString GetPassword() const { return m_Password; } + // Take the GetPassword() value and encode it, if necessary, based on the + // password encoding conversion. + ByteString GetEncodedPassword() const; + const CPDF_Dictionary* GetTrailer() const; CPDF_Dictionary* GetMutableTrailerForTesting();
diff --git a/core/fpdfapi/parser/cpdf_security_handler.cpp b/core/fpdfapi/parser/cpdf_security_handler.cpp index 7553864..d00fd60 100644 --- a/core/fpdfapi/parser/cpdf_security_handler.cpp +++ b/core/fpdfapi/parser/cpdf_security_handler.cpp
@@ -391,8 +391,11 @@ bool CPDF_SecurityHandler::CheckPassword(const ByteString& password, bool bOwner) { - if (CheckPasswordImpl(password, bOwner)) + DCHECK_EQ(kUnknown, m_PasswordEncodingConversion); + if (CheckPasswordImpl(password, bOwner)) { + m_PasswordEncodingConversion = kNone; return true; + } ByteStringView password_view = password.AsStringView(); if (password_view.IsASCII()) @@ -400,11 +403,19 @@ if (m_Revision >= 5) { ByteString utf8_password = WideString::FromLatin1(password_view).ToUTF8(); - return CheckPasswordImpl(utf8_password, bOwner); + if (!CheckPasswordImpl(utf8_password, bOwner)) + return false; + + m_PasswordEncodingConversion = kLatin1ToUtf8; + return true; } ByteString latin1_password = WideString::FromUTF8(password_view).ToLatin1(); - return CheckPasswordImpl(latin1_password, bOwner); + if (!CheckPasswordImpl(latin1_password, bOwner)) + return false; + + m_PasswordEncodingConversion = kUtf8toLatin1; + return true; } bool CPDF_SecurityHandler::CheckPasswordImpl(const ByteString& password, @@ -507,6 +518,22 @@ return m_pEncryptDict->GetBooleanFor("EncryptMetadata", true); } +ByteString CPDF_SecurityHandler::GetEncodedPassword( + ByteStringView password) const { + switch (m_PasswordEncodingConversion) { + case CPDF_SecurityHandler::kNone: + // Do nothing. + return ByteString(password); + case CPDF_SecurityHandler::kLatin1ToUtf8: + return WideString::FromLatin1(password).ToUTF8(); + case CPDF_SecurityHandler::kUtf8toLatin1: + return WideString::FromUTF8(password).ToLatin1(); + default: + NOTREACHED(); + return ByteString(password); + }; +} + void CPDF_SecurityHandler::OnCreateInternal(CPDF_Dictionary* pEncryptDict, const CPDF_Array* pIdArray, const ByteString& user_password,
diff --git a/core/fpdfapi/parser/cpdf_security_handler.h b/core/fpdfapi/parser/cpdf_security_handler.h index 0690e6f..ee69283 100644 --- a/core/fpdfapi/parser/cpdf_security_handler.h +++ b/core/fpdfapi/parser/cpdf_security_handler.h
@@ -46,7 +46,18 @@ return m_pCryptoHandler.get(); } + // Take |password| and encode it, if necessary, based on the password encoding + // conversion. + ByteString GetEncodedPassword(ByteStringView password) const; + private: + enum PasswordEncodingConversion { + kUnknown, + kNone, + kLatin1ToUtf8, + kUtf8toLatin1, + }; + CPDF_SecurityHandler(); ~CPDF_SecurityHandler() override; @@ -84,6 +95,7 @@ uint32_t m_Permissions = 0; int m_Cipher = FXCIPHER_NONE; size_t m_KeyLen = 0; + PasswordEncodingConversion m_PasswordEncodingConversion = kUnknown; ByteString m_FileId; RetainPtr<const CPDF_Dictionary> m_pEncryptDict; std::unique_ptr<CPDF_CryptoHandler> m_pCryptoHandler;
diff --git a/core/fpdfapi/parser/cpdf_security_handler_embeddertest.cpp b/core/fpdfapi/parser/cpdf_security_handler_embeddertest.cpp index a3cf04b..7e74f8f 100644 --- a/core/fpdfapi/parser/cpdf_security_handler_embeddertest.cpp +++ b/core/fpdfapi/parser/cpdf_security_handler_embeddertest.cpp
@@ -197,15 +197,11 @@ VerifySavedHelloWorldDocumentWithPassword(kAgeLatin1); VerifySavedHelloWorldDocumentWithPassword(kAgeUTF8); -#if 0 - // TODO(crbug.com/pdfium/1440): Password is in the wrong encoding. - // Fix and enable this code. ClearString(); RemoveTrailerIdFromDocument(); EXPECT_TRUE(FPDF_SaveAsCopy(document(), this, 0)); VerifySavedHelloWorldDocumentWithPassword(kAgeLatin1); VerifySavedHelloWorldDocumentWithPassword(kAgeUTF8); -#endif } TEST_F(CPDFSecurityHandlerEmbedderTest, OwnerPasswordVersion2Latin1) { @@ -233,15 +229,11 @@ VerifySavedHelloWorldDocumentWithPassword(kAgeLatin1); VerifySavedHelloWorldDocumentWithPassword(kAgeUTF8); -#if 0 - // TODO(crbug.com/pdfium/1440): Password is in the wrong encoding. - // Fix and enable this code. ClearString(); RemoveTrailerIdFromDocument(); EXPECT_TRUE(FPDF_SaveAsCopy(document(), this, 0)); VerifySavedHelloWorldDocumentWithPassword(kAgeLatin1); VerifySavedHelloWorldDocumentWithPassword(kAgeUTF8); -#endif } TEST_F(CPDFSecurityHandlerEmbedderTest, OwnerPasswordVersion3Latin1) { @@ -292,7 +284,7 @@ #if 0 // TODO(crbug.com/1032090): This triggers an MSAN error. // Fix and enable this code. - // TODO(crbug.com/pdfium/1440): Password is in the wrong encoding. + // TODO(crbug.com/pdfium/1441): Output encryption dictionary may be bad. // Fix and enable this code. ClearString(); RemoveTrailerIdFromDocument(); @@ -334,7 +326,7 @@ #if 0 // TODO(crbug.com/1032090): This triggers an MSAN error. // Fix and enable this code. - // TODO(crbug.com/pdfium/1440): Password is in the wrong encoding. + // TODO(crbug.com/pdfium/1441): Output encryption dictionary may be bad. // Fix and enable this code. ClearString(); RemoveTrailerIdFromDocument(); @@ -354,15 +346,11 @@ VerifySavedHelloWorldDocumentWithPassword(kAgeLatin1); VerifySavedHelloWorldDocumentWithPassword(kAgeUTF8); -#if 0 - // TODO(crbug.com/pdfium/1440): Password is in the wrong encoding. - // Fix and enable this code. ClearString(); RemoveTrailerIdFromDocument(); EXPECT_TRUE(FPDF_SaveAsCopy(document(), this, 0)); VerifySavedHelloWorldDocumentWithPassword(kAgeLatin1); VerifySavedHelloWorldDocumentWithPassword(kAgeUTF8); -#endif } TEST_F(CPDFSecurityHandlerEmbedderTest, UserPasswordVersion2Latin1) { @@ -390,15 +378,11 @@ VerifySavedHelloWorldDocumentWithPassword(kAgeLatin1); VerifySavedHelloWorldDocumentWithPassword(kAgeUTF8); -#if 0 - // TODO(crbug.com/pdfium/1440): Password is in the wrong encoding. - // Fix and enable this code. ClearString(); RemoveTrailerIdFromDocument(); EXPECT_TRUE(FPDF_SaveAsCopy(document(), this, 0)); VerifySavedHelloWorldDocumentWithPassword(kAgeLatin1); VerifySavedHelloWorldDocumentWithPassword(kAgeUTF8); -#endif } TEST_F(CPDFSecurityHandlerEmbedderTest, UserPasswordVersion3Latin1) { @@ -449,7 +433,7 @@ #if 0 // TODO(crbug.com/1032090): This triggers an MSAN error. // Fix and enable this code. - // TODO(crbug.com/pdfium/1440): Password is in the wrong encoding. + // TODO(crbug.com/pdfium/1441): Output encryption dictionary may be bad. // Fix and enable this code. ClearString(); RemoveTrailerIdFromDocument(); @@ -491,7 +475,7 @@ #if 0 // TODO(crbug.com/1032090): This triggers an MSAN error. // Fix and enable this code. - // TODO(crbug.com/pdfium/1440): Password is in the wrong encoding. + // TODO(crbug.com/pdfium/1441): Output encryption dictionary may be bad. // Fix and enable this code. ClearString(); RemoveTrailerIdFromDocument();