py-pdf / pypdf

A pure-python PDF library capable of splitting, merging, cropping, and transforming the pages of PDF files
https://pypdf.readthedocs.io/en/latest/
Other
8.05k stars 1.39k forks source link

Killing Security Mutants #1025

Closed MartinThoma closed 1 year ago

MartinThoma commented 2 years ago

Mutation testing applies common mistakes to the codebase and checks if the tests capture them. Those changes are called "mutations" (or mutants). Killing a mutant means to add a test which would fail if the mutation is applied.

_writer.py::encrypt (V2)

--- PyPDF2/_writer.py
+++ PyPDF2/_writer.py
@@ -709,7 +709,7 @@
         encrypt[NameObject(SA.FILTER)] = NameObject("/Standard")
         encrypt[NameObject("/V")] = NumberObject(V)
         if V == 2:
-            encrypt[NameObject(SA.LENGTH)] = NumberObject(keylen * 8)
+            encrypt[NameObject(SA.LENGTH)] = NumberObject(keylen * 9)
         encrypt[NameObject(ED.R)] = NumberObject(rev)
         encrypt[NameObject(ED.O)] = ByteStringObject(O)
         encrypt[NameObject(ED.U)] = ByteStringObject(U)

Mutant #2325

--- PyPDF2/_security.py
+++ PyPDF2/_security.py
@@ -78,7 +78,7 @@
     m.update(id1_entry.original_bytes)
     # 6. (Revision 3 or greater) If document metadata is not being encrypted,
     # pass 4 bytes with the value 0xFFFFFFFF to the MD5 hash function.
-    if rev >= 3 and not metadata_encrypt:
+    if rev > 3 and not metadata_encrypt:
         m.update(b"\xff\xff\xff\xff")
     # 7. Finish the hash.
     md5_hash = m.digest()

Mutant 2326

--- PyPDF2/_security.py
+++ PyPDF2/_security.py
@@ -78,7 +78,7 @@
     m.update(id1_entry.original_bytes)
     # 6. (Revision 3 or greater) If document metadata is not being encrypted,
     # pass 4 bytes with the value 0xFFFFFFFF to the MD5 hash function.
-    if rev >= 3 and not metadata_encrypt:
+    if rev >= 4 and not metadata_encrypt:
         m.update(b"\xff\xff\xff\xff")
     # 7. Finish the hash.
     md5_hash = m.digest()

Mutant 2337

--- PyPDF2/_security.py
+++ PyPDF2/_security.py
@@ -106,7 +106,7 @@
     key = _alg33_1(owner_pwd, rev, keylen)
     # 5. Pad or truncate the user password string as described in step 1 of
     # algorithm 3.2.
-    user_pwd_bytes = b_((user_pwd + str_(_encryption_padding))[:32])
+    user_pwd_bytes = b_((user_pwd + str_(_encryption_padding))[:33])
     # 6. Encrypt the result of step 5, using an RC4 encryption function with
     # the encryption key obtained in step 4.
     val = RC4_encrypt(key, user_pwd_bytes)

Mutant 2340 :heavy_check_mark:

--- PyPDF2/_security.py
+++ PyPDF2/_security.py
@@ -116,7 +116,7 @@
     # taking each byte of the encryption key obtained in step 4 and performing
     # an XOR operation between that byte and the single-byte value of the
     # iteration counter (from 1 to 19).
-    if rev >= 3:
+    if rev > 3:
         for i in range(1, 20):
             new_key = ""
             for key_char in key:

Mutant 2341 :heavy_check_mark:

--- PyPDF2/_security.py
+++ PyPDF2/_security.py
@@ -116,7 +116,7 @@
     # taking each byte of the encryption key obtained in step 4 and performing
     # an XOR operation between that byte and the single-byte value of the
     # iteration counter (from 1 to 19).
-    if rev >= 3:
+    if rev >= 4:
         for i in range(1, 20):
             new_key = ""
             for key_char in key:

Mutant 2342 :heavy_check_mark:

--- PyPDF2/_security.py
+++ PyPDF2/_security.py
@@ -117,7 +117,7 @@
     # an XOR operation between that byte and the single-byte value of the
     # iteration counter (from 1 to 19).
     if rev >= 3:
-        for i in range(1, 20):
+        for i in range(2, 20):
             new_key = ""
             for key_char in key:
                 new_key += chr(ord_(key_char) ^ i)

Mutant 2343

--- PyPDF2/_security.py
+++ PyPDF2/_security.py
@@ -117,7 +117,7 @@
     # an XOR operation between that byte and the single-byte value of the
     # iteration counter (from 1 to 19).
     if rev >= 3:
-        for i in range(1, 20):
+        for i in range(1, 21):
             new_key = ""
             for key_char in key:
                 new_key += chr(ord_(key_char) ^ i)

Mutant 2383 :heavy_check_mark:

--- PyPDF2/_security.py
+++ PyPDF2/_security.py
@@ -226,7 +226,7 @@

 def RC4_encrypt(key: Union[str, bytes], plaintext: bytes) -> bytes:  # TODO
-    S = list(range(256))
+    S = list(range(257))
     j = 0
     for i in range(256):
         j = (j + S[i] + ord_(key[i % len(key)])) % 256
MartinThoma commented 2 years ago
MartinThoma commented 1 year ago

I don't think we will make progress with this any time soon