1# SPDX-FileCopyrightText: 2022 James R. Barlow
2# SPDX-License-Identifier: MPL-2.0
3
4"""For managing PDF encryption."""
5
6from __future__ import annotations
7
8from typing import TYPE_CHECKING, Any, Literal, NamedTuple, cast
9
10if TYPE_CHECKING:
11 from pikepdf._core import EncryptionMethod
12
13
14class Permissions(NamedTuple):
15 """Stores the user-level permissions for an encrypted PDF.
16
17 A compliant PDF reader/writer should enforce these restrictions on people
18 who have the user password and not the owner password. In practice, either
19 password is sufficient to decrypt all document contents. A person who has
20 the owner password should be allowed to modify the document in any way.
21 pikepdf does not enforce the restrictions in any way; it is up to application
22 developers to enforce them as they see fit.
23
24 Unencrypted PDFs implicitly have all permissions allowed. Permissions can
25 only be changed when a PDF is saved.
26 """
27
28 accessibility: bool = True
29 """Deprecated in PDF 2.0. Formerly used to block accessibility tools.
30
31 In older versions of the PDF specification, it was possible to request
32 a PDF reader to block a user's right to use accessibility tools. Modern
33 PDF readers do not support this archaic feature and always allow accessibility
34 tools to be used. The only purpose of this permission is to provide
35 testing of this deprecated feature.
36 """
37
38 extract: bool = True
39 """Can users extract contents?"""
40
41 modify_annotation: bool = True
42 """Can users modify annotations?"""
43
44 modify_assembly: bool = False
45 """Can users arrange document contents?"""
46
47 modify_form: bool = True
48 """Can users fill out forms?"""
49
50 modify_other: bool = True
51 """Can users modify the document?"""
52
53 print_lowres: bool = True
54 """Can users print the document at low resolution?"""
55
56 print_highres: bool = True
57 """Can users print the document at high resolution?"""
58
59
60DEFAULT_PERMISSIONS = Permissions()
61
62
63class EncryptionInfo:
64 """Reports encryption information for an encrypted PDF.
65
66 This information may not be changed, except when a PDF is saved.
67 This object is not used to specify the encryption settings to save
68 a PDF, due to non-overlapping information requirements.
69 """
70
71 def __init__(self, encdict: dict[str, Any]):
72 """Initialize EncryptionInfo.
73
74 Generally pikepdf will initialize and return it.
75
76 Args:
77 encdict: Python dictionary containing encryption settings.
78 """
79 self._encdict = encdict
80
81 @property
82 def R(self) -> int:
83 """Revision number of the security handler."""
84 return int(self._encdict['R'])
85
86 @property
87 def V(self) -> int:
88 """Version of PDF password algorithm."""
89 return int(self._encdict['V'])
90
91 @property
92 def P(self) -> int:
93 """Return encoded permission bits.
94
95 See :meth:`Pdf.allow` instead.
96 """
97 return int(self._encdict['P'])
98
99 @property
100 def stream_method(self) -> EncryptionMethod:
101 """Encryption method used to encode streams."""
102 return cast('EncryptionMethod', self._encdict['stream'])
103
104 @property
105 def string_method(self) -> EncryptionMethod:
106 """Encryption method used to encode strings."""
107 return cast('EncryptionMethod', self._encdict['string'])
108
109 @property
110 def file_method(self) -> EncryptionMethod:
111 """Encryption method used to encode the whole file."""
112 return cast('EncryptionMethod', self._encdict['file'])
113
114 @property
115 def user_password(self) -> bytes:
116 """If possible, return the user password.
117
118 The user password can only be retrieved when a PDF is opened
119 with the owner password and when older versions of the
120 encryption algorithm are used.
121
122 The password is always returned as ``bytes`` even if it has
123 a clear Unicode representation.
124 """
125 return bytes(self._encdict['user_passwd'])
126
127 @property
128 def encryption_key(self) -> bytes:
129 """Return the RC4 or AES encryption key used for this file."""
130 return bytes(self._encdict['encryption_key'])
131
132 @property
133 def bits(self) -> int:
134 """Return the number of bits in the encryption algorithm.
135
136 e.g. if the algorithm is AES-256, this returns 256.
137 """
138 return len(self._encdict['encryption_key']) * 8
139
140 def __repr__(self):
141 return (
142 f'<{self.__class__.__name__}: {self.R=}, {self.V=}, {self.P=} '
143 f'{self.stream_method=}, {self.string_method=}, '
144 f'{self.file_method=}, {self.user_password=}, '
145 f'{self.encryption_key=}, {self.bits=}>'
146 )
147
148
149class Encryption(NamedTuple):
150 """Specify the encryption settings to apply when a PDF is saved."""
151
152 owner: str = ''
153 """The owner password to use. This allows full control
154 of the file. If blank, the PDF will be encrypted and
155 present as "(SECURED)" in PDF viewers. If the owner password
156 is blank, the user password should be as well."""
157
158 user: str = ''
159 """The user password to use. With this password, some
160 restrictions will be imposed by a typical PDF reader.
161 If blank, the PDF can be opened by anyone, but only modified
162 as allowed by the permissions in ``allow``."""
163
164 R: Literal[2, 3, 4, 5, 6] = 6
165 """Select the security handler algorithm to use. Choose from:
166 ``2``, ``3``, ``4`` or ``6``. By default, the highest version of
167 is selected (``6``). ``5`` is a deprecated algorithm that should
168 not be used."""
169
170 allow: Permissions = DEFAULT_PERMISSIONS
171 """The permissions to set.
172 If omitted, all permissions are granted to the user."""
173
174 aes: bool = True
175 """If True, request the AES algorithm. If False, use RC4.
176 If omitted, AES is selected whenever possible (R >= 4)."""
177
178 metadata: bool = True
179 """If True, also encrypt the PDF metadata. If False,
180 metadata is not encrypted. Reading document metadata without
181 decryption may be desirable in some cases. Requires ``aes=True``.
182 If omitted, metadata is encrypted whenever possible."""