/src/qpdf/libqpdf/Pl_AES_PDF.cc
Line | Count | Source (jump to first uncovered line) |
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.53M | Pipeline(identifier, next), |
14 | 6.53M | key(key), |
15 | 6.53M | crypto(QPDFCryptoProvider::getImpl()), |
16 | 6.53M | encrypt(encrypt) |
17 | 6.53M | { |
18 | 6.53M | if (!next) { |
19 | 0 | throw std::logic_error("Attempt to create Pl_AES_PDF with nullptr as next"); |
20 | 0 | } |
21 | 6.53M | if (!(key.size() == 32 || key.size() == 16)) { |
22 | 19 | throw std::runtime_error("unsupported key length"); |
23 | 19 | } |
24 | 6.53M | std::memset(this->inbuf, 0, this->buf_size); |
25 | 6.53M | std::memset(this->outbuf, 0, this->buf_size); |
26 | 6.53M | std::memset(this->cbc_block, 0, this->buf_size); |
27 | 6.53M | } |
28 | | |
29 | | void |
30 | | Pl_AES_PDF::useZeroIV() |
31 | 58.3k | { |
32 | 58.3k | use_zero_iv = true; |
33 | 58.3k | } |
34 | | |
35 | | void |
36 | | Pl_AES_PDF::disablePadding() |
37 | 6.25M | { |
38 | 6.25M | disable_padding = true; |
39 | 6.25M | } |
40 | | |
41 | | void |
42 | | Pl_AES_PDF::setIV(unsigned char const* iv, size_t bytes) |
43 | 6.19M | { |
44 | 6.19M | 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.19M | use_specified_iv = true; |
51 | 6.19M | memcpy(specified_iv, iv, bytes); |
52 | 6.19M | } |
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 | 396M | { |
69 | 396M | size_t bytes_left = len; |
70 | 396M | unsigned char const* p = data; |
71 | | |
72 | 2.53G | while (bytes_left > 0) { |
73 | 2.14G | if (offset == buf_size) { |
74 | 1.84G | flush(false); |
75 | 1.84G | } |
76 | | |
77 | 2.14G | size_t available = buf_size - offset; |
78 | 2.14G | size_t bytes = (bytes_left < available ? bytes_left : available); |
79 | 2.14G | bytes_left -= bytes; |
80 | 2.14G | std::memcpy(inbuf + offset, p, bytes); |
81 | 2.14G | offset += bytes; |
82 | 2.14G | p += bytes; |
83 | 2.14G | } |
84 | 396M | } |
85 | | |
86 | | void |
87 | | Pl_AES_PDF::finish() |
88 | 6.53M | { |
89 | 6.53M | if (encrypt) { |
90 | 6.41M | if (offset == buf_size) { |
91 | 6.26M | flush(false); |
92 | 6.26M | } |
93 | 6.41M | 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 | 169k | unsigned char pad = QIntC::to_uchar(buf_size - offset); |
97 | 169k | memset(inbuf + offset, pad, pad); |
98 | 169k | offset = buf_size; |
99 | 169k | flush(false); |
100 | 169k | } |
101 | 6.41M | } else { |
102 | 113k | 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 | 94.9k | if (offset >= buf_size) { |
107 | 0 | throw std::logic_error("buffer overflow in AES encryption pipeline"); |
108 | 0 | } |
109 | 94.9k | std::memset(inbuf + offset, 0, buf_size - offset); |
110 | 94.9k | offset = buf_size; |
111 | 94.9k | } |
112 | 113k | flush(!disable_padding); |
113 | 113k | } |
114 | 6.53M | crypto->rijndael_finalize(); |
115 | 6.53M | next()->finish(); |
116 | 6.53M | } |
117 | | |
118 | | void |
119 | | Pl_AES_PDF::initializeVector() |
120 | 6.42M | { |
121 | 6.42M | if (use_zero_iv) { |
122 | 992k | for (unsigned int i = 0; i < buf_size; ++i) { |
123 | 933k | cbc_block[i] = 0; |
124 | 933k | } |
125 | 6.36M | } else if (use_specified_iv) { |
126 | 6.19M | std::memcpy(cbc_block, specified_iv, buf_size); |
127 | 6.19M | } 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 | 169k | } else { |
132 | 169k | QUtil::initializeWithRandomBytes(cbc_block, buf_size); |
133 | 169k | } |
134 | 6.42M | } |
135 | | |
136 | | void |
137 | | Pl_AES_PDF::flush(bool strip_padding) |
138 | 1.85G | { |
139 | 1.85G | if (offset != buf_size) { |
140 | 0 | throw std::logic_error("AES pipeline: flush called when buffer was not full"); |
141 | 0 | } |
142 | | |
143 | 1.85G | if (first) { |
144 | 6.53M | first = false; |
145 | 6.53M | bool return_after_init = false; |
146 | 6.53M | if (cbc_mode) { |
147 | 6.53M | if (encrypt) { |
148 | | // Set cbc_block to the initialization vector, and if not zero, write it to the |
149 | | // output stream. |
150 | 6.41M | initializeVector(); |
151 | 6.41M | if (!(use_zero_iv || use_specified_iv)) { |
152 | 169k | next()->write(cbc_block, buf_size); |
153 | 169k | } |
154 | 6.41M | } 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.34k | initializeVector(); |
158 | 106k | } else { |
159 | | // Take the first block of input as the initialization vector. There's nothing to |
160 | | // write at this time. |
161 | 106k | memcpy(cbc_block, inbuf, buf_size); |
162 | 106k | offset = 0; |
163 | 106k | return_after_init = true; |
164 | 106k | } |
165 | 6.53M | } |
166 | 6.53M | crypto->rijndael_init( |
167 | 6.53M | encrypt, |
168 | 6.53M | reinterpret_cast<const unsigned char*>(key.data()), |
169 | 6.53M | key.size(), |
170 | 6.53M | cbc_mode, |
171 | 6.53M | cbc_block); |
172 | 6.53M | if (return_after_init) { |
173 | 106k | return; |
174 | 106k | } |
175 | 6.53M | } |
176 | | |
177 | 1.85G | crypto->rijndael_process(inbuf, outbuf); |
178 | 1.85G | unsigned int bytes = buf_size; |
179 | 1.85G | if (strip_padding) { |
180 | 75.2k | unsigned char last = outbuf[buf_size - 1]; |
181 | 75.2k | if (last <= buf_size) { |
182 | 14.2k | bool strip = true; |
183 | 50.0k | for (unsigned int i = 1; i <= last; ++i) { |
184 | 42.1k | if (outbuf[buf_size - i] != last) { |
185 | 6.45k | strip = false; |
186 | 6.45k | break; |
187 | 6.45k | } |
188 | 42.1k | } |
189 | 14.2k | if (strip) { |
190 | 7.84k | bytes -= last; |
191 | 7.84k | } |
192 | 14.2k | } |
193 | 75.2k | } |
194 | 1.85G | offset = 0; |
195 | 1.85G | next()->write(outbuf, bytes); |
196 | 1.85G | } |