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