Coverage Report

Created: 2025-11-11 07:06

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 <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
}