Skip to content
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

TST: No pycryptodome #1050

Merged
merged 13 commits into from
Jul 3, 2022
9 changes: 8 additions & 1 deletion .github/workflows/github-ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,10 @@ jobs:
strategy:
matrix:
python-version: ["3.6", "3.7", "3.8", "3.9", "3.10"]

use-cryptodome: [""]
include:
- python-version: "3.10"
use-cryptodome: "false"
steps:
- name: Checkout Code
uses: actions/checkout@v3
Expand All @@ -38,6 +41,10 @@ jobs:
- name: Install requirements (Python 3)
run: |
pip install -r requirements/ci.txt
- name: Remove cryptodome
run: |
pip uninstall pycryptodome -y
if: matrix.use-cryptodome == false
- name: Install PyPDF2
run: |
pip install .
Expand Down
2 changes: 1 addition & 1 deletion PyPDF2/_encryption.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ class CryptIdentity(CryptBase):


try:
from Crypto.Cipher import AES, ARC4
from Crypto.Cipher import AES, ARC4 # type: ignore[import]

class CryptRC4(CryptBase):
def __init__(self, key: bytes) -> None:
Expand Down
80 changes: 58 additions & 22 deletions tests/test_encryption.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,53 +3,69 @@
import pytest

import PyPDF2
from PyPDF2.errors import DependencyError

try:
from Crypto.Cipher import AES # noqa: F401

HAS_PYCRYPTODOME = True
except ImportError:
HAS_PYCRYPTODOME = False

TESTS_ROOT = os.path.abspath(os.path.dirname(__file__))
PROJECT_ROOT = os.path.dirname(TESTS_ROOT)
RESOURCE_ROOT = os.path.join(PROJECT_ROOT, "resources")


@pytest.mark.parametrize(
"name",
("name", "requres_pycryptodome"),
[
# unencrypted pdf
"unencrypted.pdf",
("unencrypted.pdf", False),
# created by `qpdf --encrypt "" "" 40 -- unencrypted.pdf r2-empty-password.pdf`
"r2-empty-password.pdf",
("r2-empty-password.pdf", False),
# created by `qpdf --encrypt "" "" 128 -- unencrypted.pdf r3-empty-password.pdf`
"r3-empty-password.pdf",
("r3-empty-password.pdf", False),
# created by `qpdf --encrypt "asdfzxcv" "" 40 -- unencrypted.pdf r2-user-password.pdf`
"r2-user-password.pdf",
("r2-user-password.pdf", False),
# created by `qpdf --encrypt "asdfzxcv" "" 128 -- unencrypted.pdf r3-user-password.pdf`
"r3-user-password.pdf",
("r3-user-password.pdf", False),
# created by `qpdf --encrypt "asdfzxcv" "" 128 --force-V4 -- unencrypted.pdf r4-user-password.pdf`
"r4-user-password.pdf",
("r4-user-password.pdf", False),
# created by `qpdf --encrypt "asdfzxcv" "" 128 --use-aes=y -- unencrypted.pdf r4-aes-user-password.pdf`
"r4-aes-user-password.pdf",
("r4-aes-user-password.pdf", True),
# # created by `qpdf --encrypt "" "" 256 --force-R5 -- unencrypted.pdf r5-empty-password.pdf`
"r5-empty-password.pdf",
("r5-empty-password.pdf", True),
# # created by `qpdf --encrypt "asdfzxcv" "" 256 --force-R5 -- unencrypted.pdf r5-user-password.pdf`
"r5-user-password.pdf",
("r5-user-password.pdf", True),
# # created by `qpdf --encrypt "" "asdfzxcv" 256 --force-R5 -- unencrypted.pdf r5-owner-password.pdf`
"r5-owner-password.pdf",
("r5-owner-password.pdf", True),
# created by `qpdf --encrypt "" "" 256 -- unencrypted.pdf r6-empty-password.pdf`
"r6-empty-password.pdf",
("r6-empty-password.pdf", True),
# created by `qpdf --encrypt "asdfzxcv" "" 256 -- unencrypted.pdf r6-user-password.pdf`
"r6-user-password.pdf",
("r6-user-password.pdf", True),
# created by `qpdf --encrypt "" "asdfzxcv" 256 -- unencrypted.pdf r6-owner-password.pdf`
"r6-owner-password.pdf",
("r6-owner-password.pdf", True),
],
)
def test_encryption(name):
def test_encryption(name, requres_pycryptodome):
inputfile = os.path.join(RESOURCE_ROOT, "encryption", name)
ipdf = PyPDF2.PdfReader(inputfile)
if inputfile.endswith("unencrypted.pdf"):
assert not ipdf.is_encrypted
if requres_pycryptodome and not HAS_PYCRYPTODOME:
with pytest.raises(DependencyError) as exc:
ipdf = PyPDF2.PdfReader(inputfile)
ipdf.decrypt("asdfzxcv")
dd = dict(ipdf.metadata)
assert exc.value.args[0] == "PyCryptodome is required for AES algorithm"
return
else:
assert ipdf.is_encrypted
ipdf.decrypt("asdfzxcv")
assert len(ipdf.pages) == 1
dd = dict(ipdf.metadata)
ipdf = PyPDF2.PdfReader(inputfile)
if inputfile.endswith("unencrypted.pdf"):
assert not ipdf.is_encrypted
else:
assert ipdf.is_encrypted
ipdf.decrypt("asdfzxcv")
assert len(ipdf.pages) == 1
dd = dict(ipdf.metadata)
# remove empty value entry
dd = {x[0]: x[1] for x in dd.items() if x[1]}
assert dd == {
Expand All @@ -69,6 +85,7 @@ def test_encryption(name):
("r6-both-passwords.pdf", "foo", "bar"),
],
)
@pytest.mark.skipif(not HAS_PYCRYPTODOME, reason="No pycryptodome")
def test_both_password(name, user_passwd, owner_passwd):
from PyPDF2 import PasswordType

Expand All @@ -80,6 +97,24 @@ def test_both_password(name, user_passwd, owner_passwd):
assert len(ipdf.pages) == 1


@pytest.mark.parametrize(
("pdffile", "password"),
[
("crazyones-encrypted-256.pdf", "password"),
],
)
@pytest.mark.skipif(not HAS_PYCRYPTODOME, reason="No pycryptodome")
def test_get_page_of_encrypted_file_new_algorithm(pdffile, password):
"""
Check if we can read a page of an encrypted file.

This is a regression test for issue 327:
IndexError for get_page() of decrypted file
"""
path = os.path.join(RESOURCE_ROOT, pdffile)
PyPDF2.PdfReader(path, password=password).pages[0]


@pytest.mark.parametrize(
"names",
[
Expand All @@ -93,6 +128,7 @@ def test_both_password(name, user_passwd, owner_passwd):
),
],
)
@pytest.mark.skipif(not HAS_PYCRYPTODOME, reason="No pycryptodome")
def test_encryption_merge(names):
pdf_merger = PyPDF2.PdfMerger()
files = [os.path.join(RESOURCE_ROOT, "encryption", x) for x in names]
Expand Down
17 changes: 0 additions & 17 deletions tests/test_reader.py
Original file line number Diff line number Diff line change
Expand Up @@ -280,23 +280,6 @@ def test_get_page_of_encrypted_file(pdffile, password, should_fail):
PdfReader(path, password=password).pages[0]


@pytest.mark.parametrize(
("pdffile", "password"),
[
("crazyones-encrypted-256.pdf", "password"),
],
)
def test_get_page_of_encrypted_file_new_algorithm(pdffile, password):
"""
Check if we can read a page of an encrypted file.

This is a regression test for issue 327:
IndexError for get_page() of decrypted file
"""
path = os.path.join(RESOURCE_ROOT, pdffile)
PdfReader(path, password=password).pages[0]


@pytest.mark.parametrize(
("src", "expected", "expected_get_fields"),
[
Expand Down