/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 | 0 | Pipeline(identifier, next), |
19 | 0 | crypto(QPDFCryptoProvider::getImpl()), |
20 | 0 | encrypt(encrypt), |
21 | 0 | key_bytes(key_bytes) |
22 | 0 | { |
23 | 0 | if (!next) { |
24 | 0 | throw std::logic_error("Attempt to create Pl_AES_PDF with nullptr as next"); |
25 | 0 | } |
26 | 0 | if (!(key_bytes == 32 || key_bytes == 16)) { |
27 | 0 | throw std::runtime_error("unsupported key length"); |
28 | 0 | } |
29 | 0 | this->key = std::make_unique<unsigned char[]>(key_bytes); |
30 | 0 | std::memcpy(this->key.get(), key, key_bytes); |
31 | 0 | std::memset(this->inbuf, 0, this->buf_size); |
32 | 0 | std::memset(this->outbuf, 0, this->buf_size); |
33 | 0 | std::memset(this->cbc_block, 0, this->buf_size); |
34 | 0 | } |
35 | | |
36 | | void |
37 | | Pl_AES_PDF::useZeroIV() |
38 | 0 | { |
39 | 0 | use_zero_iv = true; |
40 | 0 | } |
41 | | |
42 | | void |
43 | | Pl_AES_PDF::disablePadding() |
44 | 0 | { |
45 | 0 | disable_padding = true; |
46 | 0 | } |
47 | | |
48 | | void |
49 | | Pl_AES_PDF::setIV(unsigned char const* iv, size_t bytes) |
50 | 0 | { |
51 | 0 | if (bytes != 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 | 0 | use_specified_iv = true; |
58 | 0 | memcpy(specified_iv, iv, bytes); |
59 | 0 | } |
60 | | |
61 | | void |
62 | | Pl_AES_PDF::disableCBC() |
63 | 0 | { |
64 | 0 | 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 | 0 | { |
76 | 0 | size_t bytes_left = len; |
77 | 0 | unsigned char const* p = data; |
78 | |
|
79 | 0 | while (bytes_left > 0) { |
80 | 0 | if (offset == buf_size) { |
81 | 0 | flush(false); |
82 | 0 | } |
83 | |
|
84 | 0 | size_t available = buf_size - offset; |
85 | 0 | size_t bytes = (bytes_left < available ? bytes_left : available); |
86 | 0 | bytes_left -= bytes; |
87 | 0 | std::memcpy(inbuf + offset, p, bytes); |
88 | 0 | offset += bytes; |
89 | 0 | p += bytes; |
90 | 0 | } |
91 | 0 | } |
92 | | |
93 | | void |
94 | | Pl_AES_PDF::finish() |
95 | 0 | { |
96 | 0 | if (encrypt) { |
97 | 0 | if (offset == buf_size) { |
98 | 0 | flush(false); |
99 | 0 | } |
100 | 0 | if (!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 | 0 | unsigned char pad = QIntC::to_uchar(buf_size - offset); |
104 | 0 | memset(inbuf + offset, pad, pad); |
105 | 0 | offset = buf_size; |
106 | 0 | flush(false); |
107 | 0 | } |
108 | 0 | } else { |
109 | 0 | if (offset != 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 | 0 | if (offset >= buf_size) { |
114 | 0 | throw std::logic_error("buffer overflow in AES encryption pipeline"); |
115 | 0 | } |
116 | 0 | std::memset(inbuf + offset, 0, buf_size - offset); |
117 | 0 | offset = buf_size; |
118 | 0 | } |
119 | 0 | flush(!disable_padding); |
120 | 0 | } |
121 | 0 | crypto->rijndael_finalize(); |
122 | 0 | next()->finish(); |
123 | 0 | } |
124 | | |
125 | | void |
126 | | Pl_AES_PDF::initializeVector() |
127 | 0 | { |
128 | 0 | if (use_zero_iv) { |
129 | 0 | for (unsigned int i = 0; i < buf_size; ++i) { |
130 | 0 | cbc_block[i] = 0; |
131 | 0 | } |
132 | 0 | } else if (use_specified_iv) { |
133 | 0 | std::memcpy(cbc_block, specified_iv, buf_size); |
134 | 0 | } else if (use_static_iv) { |
135 | 0 | for (unsigned int i = 0; i < buf_size; ++i) { |
136 | 0 | cbc_block[i] = static_cast<unsigned char>(14U * (1U + i)); |
137 | 0 | } |
138 | 0 | } else { |
139 | 0 | QUtil::initializeWithRandomBytes(cbc_block, buf_size); |
140 | 0 | } |
141 | 0 | } |
142 | | |
143 | | void |
144 | | Pl_AES_PDF::flush(bool strip_padding) |
145 | 0 | { |
146 | 0 | if (offset != buf_size) { |
147 | 0 | throw std::logic_error("AES pipeline: flush called when buffer was not full"); |
148 | 0 | } |
149 | | |
150 | 0 | if (first) { |
151 | 0 | first = false; |
152 | 0 | bool return_after_init = false; |
153 | 0 | if (cbc_mode) { |
154 | 0 | if (encrypt) { |
155 | | // Set cbc_block to the initialization vector, and if not zero, write it to the |
156 | | // output stream. |
157 | 0 | initializeVector(); |
158 | 0 | if (!(use_zero_iv || use_specified_iv)) { |
159 | 0 | next()->write(cbc_block, buf_size); |
160 | 0 | } |
161 | 0 | } else if (use_zero_iv || use_specified_iv) { |
162 | | // Initialize vector with zeroes; zero vector was not written to the beginning of |
163 | | // the input file. |
164 | 0 | initializeVector(); |
165 | 0 | } else { |
166 | | // Take the first block of input as the initialization vector. There's nothing to |
167 | | // write at this time. |
168 | 0 | memcpy(cbc_block, inbuf, buf_size); |
169 | 0 | offset = 0; |
170 | 0 | return_after_init = true; |
171 | 0 | } |
172 | 0 | } |
173 | 0 | crypto->rijndael_init(encrypt, key.get(), key_bytes, cbc_mode, cbc_block); |
174 | 0 | if (return_after_init) { |
175 | 0 | return; |
176 | 0 | } |
177 | 0 | } |
178 | | |
179 | 0 | crypto->rijndael_process(inbuf, outbuf); |
180 | 0 | unsigned int bytes = buf_size; |
181 | 0 | if (strip_padding) { |
182 | 0 | unsigned char last = outbuf[buf_size - 1]; |
183 | 0 | if (last <= buf_size) { |
184 | 0 | bool strip = true; |
185 | 0 | for (unsigned int i = 1; i <= last; ++i) { |
186 | 0 | if (outbuf[buf_size - i] != last) { |
187 | 0 | strip = false; |
188 | 0 | break; |
189 | 0 | } |
190 | 0 | } |
191 | 0 | if (strip) { |
192 | 0 | bytes -= last; |
193 | 0 | } |
194 | 0 | } |
195 | 0 | } |
196 | 0 | offset = 0; |
197 | 0 | next()->write(outbuf, bytes); |
198 | 0 | } |