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();