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