Coverage Report

Created: 2025-07-18 06:59

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