/src/qpdf/libqpdf/Pl_AES_PDF.cc
Line | Count | Source |
1 | | #include <qpdf/Pl_AES_PDF.hh> |
2 | | |
3 | | #include <qpdf/QIntC.hh> |
4 | | #include <qpdf/QPDFCryptoProvider.hh> |
5 | | #include <qpdf/QUtil.hh> |
6 | | #include <cstring> |
7 | | #include <stdexcept> |
8 | | #include <string> |
9 | | |
10 | | bool Pl_AES_PDF::use_static_iv = false; |
11 | | |
12 | | Pl_AES_PDF::Pl_AES_PDF(char const* identifier, Pipeline* next, bool encrypt, std::string key) : |
13 | 6.79M | Pipeline(identifier, next), |
14 | 6.79M | key(key), |
15 | 6.79M | crypto(QPDFCryptoProvider::getImpl()), |
16 | 6.79M | encrypt(encrypt) |
17 | 6.79M | { |
18 | 6.79M | if (!next) { |
19 | 0 | throw std::logic_error("Attempt to create Pl_AES_PDF with nullptr as next"); |
20 | 0 | } |
21 | 6.79M | if (!(key.size() == 32 || key.size() == 16)) { |
22 | 24 | throw std::runtime_error("unsupported key length"); |
23 | 24 | } |
24 | 6.79M | std::memset(this->inbuf, 0, this->buf_size); |
25 | 6.79M | std::memset(this->outbuf, 0, this->buf_size); |
26 | 6.79M | std::memset(this->cbc_block, 0, this->buf_size); |
27 | 6.79M | } |
28 | | |
29 | | void |
30 | | Pl_AES_PDF::useZeroIV() |
31 | 61.4k | { |
32 | 61.4k | use_zero_iv = true; |
33 | 61.4k | } |
34 | | |
35 | | void |
36 | | Pl_AES_PDF::disablePadding() |
37 | 6.51M | { |
38 | 6.51M | disable_padding = true; |
39 | 6.51M | } |
40 | | |
41 | | void |
42 | | Pl_AES_PDF::setIV(unsigned char const* iv, size_t bytes) |
43 | 6.45M | { |
44 | 6.45M | if (bytes != buf_size) { |
45 | 0 | throw std::logic_error( |
46 | 0 | "Pl_AES_PDF: specified initialization vector" |
47 | 0 | " size in bytes must be " + |
48 | 0 | std::to_string(bytes)); |
49 | 0 | } |
50 | 6.45M | use_specified_iv = true; |
51 | 6.45M | memcpy(specified_iv, iv, bytes); |
52 | 6.45M | } |
53 | | |
54 | | void |
55 | | Pl_AES_PDF::disableCBC() |
56 | 0 | { |
57 | 0 | cbc_mode = false; |
58 | 0 | } |
59 | | |
60 | | void |
61 | | Pl_AES_PDF::useStaticIV() |
62 | 0 | { |
63 | 0 | use_static_iv = true; |
64 | 0 | } |
65 | | |
66 | | void |
67 | | Pl_AES_PDF::write(unsigned char const* data, size_t len) |
68 | 413M | { |
69 | 413M | size_t bytes_left = len; |
70 | 413M | unsigned char const* p = data; |
71 | | |
72 | 2.63G | while (bytes_left > 0) { |
73 | 2.22G | if (offset == buf_size) { |
74 | 1.90G | flush(false); |
75 | 1.90G | } |
76 | | |
77 | 2.22G | size_t available = buf_size - offset; |
78 | 2.22G | size_t bytes = (bytes_left < available ? bytes_left : available); |
79 | 2.22G | bytes_left -= bytes; |
80 | 2.22G | std::memcpy(inbuf + offset, p, bytes); |
81 | 2.22G | offset += bytes; |
82 | 2.22G | p += bytes; |
83 | 2.22G | } |
84 | 413M | } |
85 | | |
86 | | void |
87 | | Pl_AES_PDF::finish() |
88 | 6.79M | { |
89 | 6.79M | if (encrypt) { |
90 | 6.66M | if (offset == buf_size) { |
91 | 6.52M | flush(false); |
92 | 6.52M | } |
93 | 6.66M | if (!disable_padding) { |
94 | | // Pad as described in section 3.5.1 of version 1.7 of the PDF specification, including |
95 | | // providing an entire block of padding if the input was a multiple of 16 bytes. |
96 | 153k | unsigned char pad = QIntC::to_uchar(buf_size - offset); |
97 | 153k | memset(inbuf + offset, pad, pad); |
98 | 153k | offset = buf_size; |
99 | 153k | flush(false); |
100 | 153k | } |
101 | 6.66M | } else { |
102 | 138k | if (offset != buf_size) { |
103 | | // This is never supposed to happen as the output is always supposed to be padded. |
104 | | // However, we have encountered files for which the output is not a multiple of the |
105 | | // block size. In this case, pad with zeroes and hope for the best. |
106 | 121k | if (offset >= buf_size) { |
107 | 0 | throw std::logic_error("buffer overflow in AES encryption pipeline"); |
108 | 0 | } |
109 | 121k | std::memset(inbuf + offset, 0, buf_size - offset); |
110 | 121k | offset = buf_size; |
111 | 121k | } |
112 | 138k | flush(!disable_padding); |
113 | 138k | } |
114 | 6.79M | crypto->rijndael_finalize(); |
115 | 6.79M | next()->finish(); |
116 | 6.79M | } |
117 | | |
118 | | void |
119 | | Pl_AES_PDF::initializeVector() |
120 | 6.66M | { |
121 | 6.66M | if (use_zero_iv) { |
122 | 1.04M | for (unsigned int i = 0; i < buf_size; ++i) { |
123 | 983k | cbc_block[i] = 0; |
124 | 983k | } |
125 | 6.60M | } else if (use_specified_iv) { |
126 | 6.45M | std::memcpy(cbc_block, specified_iv, buf_size); |
127 | 6.45M | } else if (use_static_iv) { |
128 | 0 | for (unsigned int i = 0; i < buf_size; ++i) { |
129 | 0 | cbc_block[i] = static_cast<unsigned char>(14U * (1U + i)); |
130 | 0 | } |
131 | 153k | } else { |
132 | 153k | QUtil::initializeWithRandomBytes(cbc_block, buf_size); |
133 | 153k | } |
134 | 6.66M | } |
135 | | |
136 | | void |
137 | | Pl_AES_PDF::flush(bool strip_padding) |
138 | 1.91G | { |
139 | 1.91G | if (offset != buf_size) { |
140 | 0 | throw std::logic_error("AES pipeline: flush called when buffer was not full"); |
141 | 0 | } |
142 | | |
143 | 1.91G | if (first) { |
144 | 6.79M | first = false; |
145 | 6.79M | bool return_after_init = false; |
146 | 6.79M | if (cbc_mode) { |
147 | 6.79M | if (encrypt) { |
148 | | // Set cbc_block to the initialization vector, and if not zero, write it to the |
149 | | // output stream. |
150 | 6.66M | initializeVector(); |
151 | 6.66M | if (!(use_zero_iv || use_specified_iv)) { |
152 | 153k | next()->write(cbc_block, buf_size); |
153 | 153k | } |
154 | 6.66M | } else if (use_zero_iv || use_specified_iv) { |
155 | | // Initialize vector with zeroes; zero vector was not written to the beginning of |
156 | | // the input file. |
157 | 6.06k | initializeVector(); |
158 | 132k | } else { |
159 | | // Take the first block of input as the initialization vector. There's nothing to |
160 | | // write at this time. |
161 | 132k | memcpy(cbc_block, inbuf, buf_size); |
162 | 132k | offset = 0; |
163 | 132k | return_after_init = true; |
164 | 132k | } |
165 | 6.79M | } |
166 | 6.79M | crypto->rijndael_init( |
167 | 6.79M | encrypt, |
168 | 6.79M | reinterpret_cast<const unsigned char*>(key.data()), |
169 | 6.79M | key.size(), |
170 | 6.79M | cbc_mode, |
171 | 6.79M | cbc_block); |
172 | 6.79M | if (return_after_init) { |
173 | 132k | return; |
174 | 132k | } |
175 | 6.79M | } |
176 | | |
177 | 1.91G | crypto->rijndael_process(inbuf, outbuf); |
178 | 1.91G | unsigned int bytes = buf_size; |
179 | 1.91G | if (strip_padding) { |
180 | 95.4k | unsigned char last = outbuf[buf_size - 1]; |
181 | 95.4k | if (last <= buf_size) { |
182 | 14.6k | bool strip = true; |
183 | 53.8k | for (unsigned int i = 1; i <= last; ++i) { |
184 | 46.8k | if (outbuf[buf_size - i] != last) { |
185 | 7.67k | strip = false; |
186 | 7.67k | break; |
187 | 7.67k | } |
188 | 46.8k | } |
189 | 14.6k | if (strip) { |
190 | 7.02k | bytes -= last; |
191 | 7.02k | } |
192 | 14.6k | } |
193 | 95.4k | } |
194 | 1.91G | offset = 0; |
195 | 1.91G | next()->write(outbuf, bytes); |
196 | 1.91G | } |