Commit b46d4b9872654a953c7aedf325d36da2d2acc589
1 parent
e68d5392
Disallow `--deterministic-id` with encrypted output and improve error handling f…
…or deterministic ID generation (fixes #1235).
Showing
7 changed files
with
53 additions
and
17 deletions
include/qpdf/QPDFWriter.hh
| ... | ... | @@ -495,7 +495,7 @@ class QPDFWriter |
| 495 | 495 | void preserveObjectStreams(); |
| 496 | 496 | void generateObjectStreams(); |
| 497 | 497 | std::string getOriginalID1(); |
| 498 | - void generateID(); | |
| 498 | + void generateID(bool encrypted); | |
| 499 | 499 | void interpretR3EncryptionParameters( |
| 500 | 500 | bool allow_accessibility, |
| 501 | 501 | bool allow_extract, | ... | ... |
libqpdf/QPDFJob_config.cc
| ... | ... | @@ -149,6 +149,9 @@ QPDFJob::Config::jpegQuality(std::string const& parameter) |
| 149 | 149 | QPDFJob::Config* |
| 150 | 150 | QPDFJob::Config::copyEncryption(std::string const& parameter) |
| 151 | 151 | { |
| 152 | + if (o.m->deterministic_id) { | |
| 153 | + usage("the deterministic-id option is incompatible with encrypted output files"); | |
| 154 | + } | |
| 152 | 155 | o.m->encryption_file = parameter; |
| 153 | 156 | o.m->copy_encryption = true; |
| 154 | 157 | o.m->encrypt = false; |
| ... | ... | @@ -168,6 +171,9 @@ QPDFJob::Config::decrypt() |
| 168 | 171 | QPDFJob::Config* |
| 169 | 172 | QPDFJob::Config::deterministicId() |
| 170 | 173 | { |
| 174 | + if (o.m->encrypt || o.m->copy_encryption) { | |
| 175 | + usage("the deterministic-id option is incompatible with encrypted output files"); | |
| 176 | + } | |
| 171 | 177 | o.m->deterministic_id = true; |
| 172 | 178 | return this; |
| 173 | 179 | } |
| ... | ... | @@ -1116,6 +1122,9 @@ std::shared_ptr<QPDFJob::EncConfig> |
| 1116 | 1122 | QPDFJob::Config::encrypt( |
| 1117 | 1123 | int keylen, std::string const& user_password, std::string const& owner_password) |
| 1118 | 1124 | { |
| 1125 | + if (o.m->deterministic_id) { | |
| 1126 | + usage("the deterministic-id option is incompatible with encrypted output files"); | |
| 1127 | + } | |
| 1119 | 1128 | o.m->keylen = keylen; |
| 1120 | 1129 | if (keylen == 256) { |
| 1121 | 1130 | o.m->use_aes = true; | ... | ... |
libqpdf/QPDFWriter.cc
| ... | ... | @@ -859,7 +859,7 @@ QPDFWriter::interpretR3EncryptionParameters( |
| 859 | 859 | void |
| 860 | 860 | QPDFWriter::setEncryptionParameters(char const* user_password, char const* owner_password) |
| 861 | 861 | { |
| 862 | - generateID(); | |
| 862 | + generateID(true); | |
| 863 | 863 | m->encryption->setId1(m->id1); |
| 864 | 864 | m->encryption_key = m->encryption->compute_parameters(user_password, owner_password); |
| 865 | 865 | setEncryptionMinimumVersion(); |
| ... | ... | @@ -871,7 +871,7 @@ QPDFWriter::copyEncryptionParameters(QPDF& qpdf) |
| 871 | 871 | m->preserve_encryption = false; |
| 872 | 872 | QPDFObjectHandle trailer = qpdf.getTrailer(); |
| 873 | 873 | if (trailer.hasKey("/Encrypt")) { |
| 874 | - generateID(); | |
| 874 | + generateID(true); | |
| 875 | 875 | m->id1 = trailer.getKey("/ID").getArrayItem(0).getStringValue(); |
| 876 | 876 | QPDFObjectHandle encrypt = trailer.getKey("/Encrypt"); |
| 877 | 877 | int V = encrypt.getKey("/V").getIntValueAsInt(); |
| ... | ... | @@ -1301,7 +1301,7 @@ QPDFWriter::writeTrailer( |
| 1301 | 1301 | if (linearization_pass == 0 && m->deterministic_id) { |
| 1302 | 1302 | computeDeterministicIDData(); |
| 1303 | 1303 | } |
| 1304 | - generateID(); | |
| 1304 | + generateID(m->encryption.get()); | |
| 1305 | 1305 | write_string(m->id1, true).write_string(m->id2, true); |
| 1306 | 1306 | } |
| 1307 | 1307 | write("]"); |
| ... | ... | @@ -1865,7 +1865,7 @@ QPDFWriter::getOriginalID1() |
| 1865 | 1865 | } |
| 1866 | 1866 | |
| 1867 | 1867 | void |
| 1868 | -QPDFWriter::generateID() | |
| 1868 | +QPDFWriter::generateID(bool encrypted) | |
| 1869 | 1869 | { |
| 1870 | 1870 | // Generate the ID lazily so that we can handle the user's preference to use static or |
| 1871 | 1871 | // deterministic ID generation. |
| ... | ... | @@ -1911,12 +1911,14 @@ QPDFWriter::generateID() |
| 1911 | 1911 | |
| 1912 | 1912 | std::string seed; |
| 1913 | 1913 | if (m->deterministic_id) { |
| 1914 | - if (m->deterministic_id_data.empty()) { | |
| 1915 | - QTC::TC("qpdf", "QPDFWriter deterministic with no data"); | |
| 1914 | + if (encrypted) { | |
| 1916 | 1915 | throw std::runtime_error( |
| 1917 | - "INTERNAL ERROR: QPDFWriter::generateID has no data for " | |
| 1918 | - "deterministic ID. This may happen if deterministic ID " | |
| 1919 | - "and file encryption are requested together."); | |
| 1916 | + "QPDFWriter: unable to generated a deterministic ID because the file to be " | |
| 1917 | + "written is encrypted (even though the file may not require a password)"); | |
| 1918 | + } | |
| 1919 | + if (m->deterministic_id_data.empty()) { | |
| 1920 | + throw std::logic_error( | |
| 1921 | + "INTERNAL ERROR: QPDFWriter::generateID has no data for deterministic ID"); | |
| 1920 | 1922 | } |
| 1921 | 1923 | seed += m->deterministic_id_data; |
| 1922 | 1924 | } else { | ... | ... |
manual/release-notes.rst
| ... | ... | @@ -23,6 +23,13 @@ more detail. |
| 23 | 23 | not work on some older Linux distributions. If you need support |
| 24 | 24 | for an older distribution, please use version 12.2.0 or below. |
| 25 | 25 | |
| 26 | + - CLI Enhancements | |
| 27 | + | |
| 28 | + - Disallow option :qpdf:ref:`--deterministic-id` to be used together | |
| 29 | + with the incompatible options :qpdf:ref:`--encrypt` or | |
| 30 | + :qpdf:ref:`--copy-encryption`. | |
| 31 | + | |
| 32 | + | |
| 26 | 33 | - Other enhancements |
| 27 | 34 | |
| 28 | 35 | - ``QPDFWriter`` will no longer add filters when writing empty streams. | ... | ... |
qpdf/qpdf.testcov
| ... | ... | @@ -254,7 +254,6 @@ QPDFJob npages 0 |
| 254 | 254 | QPDF already reserved object 0 |
| 255 | 255 | QPDFWriter standard deterministic ID 1 |
| 256 | 256 | QPDFWriter linearized deterministic ID 1 |
| 257 | -QPDFWriter deterministic with no data 0 | |
| 258 | 257 | qpdf-c called qpdf_set_deterministic_ID 0 |
| 259 | 258 | QPDFParser invalid objgen 0 |
| 260 | 259 | QPDF object id 0 0 | ... | ... |
qpdf/qtest/arg-parsing.test
| ... | ... | @@ -15,7 +15,7 @@ cleanup(); |
| 15 | 15 | |
| 16 | 16 | my $td = new TestDriver('arg-parsing'); |
| 17 | 17 | |
| 18 | -my $n_tests = 26; | |
| 18 | +my $n_tests = 30; | |
| 19 | 19 | |
| 20 | 20 | $td->runtest("required argument", |
| 21 | 21 | {$td->COMMAND => "qpdf --password minimal.pdf"}, |
| ... | ... | @@ -138,6 +138,26 @@ $td->runtest("missing key length", |
| 138 | 138 | {$td->REGEXP => ".*encryption key length is required", |
| 139 | 139 | $td->EXIT_STATUS => 2}, |
| 140 | 140 | $td->NORMALIZE_NEWLINES); |
| 141 | +$td->runtest("encrypt and deterministic-id 1", | |
| 142 | + {$td->COMMAND => "qpdf --deterministic-id --encrypt --"}, | |
| 143 | + {$td->REGEXP => ".*the deterministic-id option is incompatible with encrypted output files", | |
| 144 | + $td->EXIT_STATUS => 2}, | |
| 145 | + $td->NORMALIZE_NEWLINES); | |
| 146 | +$td->runtest("encrypt and deterministic-id 2", | |
| 147 | + {$td->COMMAND => "qpdf --deterministic-id --encrypt --bits=256 -- --deterministic-id"}, | |
| 148 | + {$td->REGEXP => ".*the deterministic-id option is incompatible with encrypted output files", | |
| 149 | + $td->EXIT_STATUS => 2}, | |
| 150 | + $td->NORMALIZE_NEWLINES); | |
| 151 | +$td->runtest("copy-encryption and deterministic-id 1", | |
| 152 | + {$td->COMMAND => "qpdf --deterministic-id --copy-encryption=missing.pdf"}, | |
| 153 | + {$td->REGEXP => ".*the deterministic-id option is incompatible with encrypted output files", | |
| 154 | + $td->EXIT_STATUS => 2}, | |
| 155 | + $td->NORMALIZE_NEWLINES); | |
| 156 | +$td->runtest("copy-encryption and deterministic-id 2", | |
| 157 | + {$td->COMMAND => "qpdf --copy-encryption=missing.pdf --deterministic-id"}, | |
| 158 | + {$td->REGEXP => ".*the deterministic-id option is incompatible with encrypted output files", | |
| 159 | + $td->EXIT_STATUS => 2}, | |
| 160 | + $td->NORMALIZE_NEWLINES); | |
| 141 | 161 | |
| 142 | 162 | # Disallow mixing positional and flag-style encryption arguments. |
| 143 | 163 | my @bad_enc = ( | ... | ... |
qpdf/qtest/deterministic-id.test
| ... | ... | @@ -54,11 +54,10 @@ foreach my $d ('nn', 'ny', 'yn', 'yy') |
| 54 | 54 | |
| 55 | 55 | $td->runtest("deterministic ID with encryption", |
| 56 | 56 | {$td->COMMAND => "qpdf -deterministic-id encrypted-with-images.pdf a.pdf"}, |
| 57 | - {$td->STRING => "qpdf: INTERNAL ERROR: QPDFWriter::generateID" . | |
| 58 | - " has no data for deterministic ID." . | |
| 59 | - " This may happen if deterministic ID and" . | |
| 60 | - " file encryption are requested together.\n", | |
| 61 | - $td->EXIT_STATUS => 2}, | |
| 57 | + {$td->STRING => "qpdf: QPDFWriter: unable to generated a deterministic ID " . | |
| 58 | + "because the file to be written is encrypted (even though the file " . | |
| 59 | + "may not require a password)\n", | |
| 60 | + $td->EXIT_STATUS => 2}, | |
| 62 | 61 | $td->NORMALIZE_NEWLINES); |
| 63 | 62 | $td->runtest("deterministic ID (C API)", |
| 64 | 63 | {$td->COMMAND => | ... | ... |