Coverage Report

Created: 2026-08-31 06:57

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