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,7 +495,7 @@ class QPDFWriter | ||
| 495 | void preserveObjectStreams(); | 495 | void preserveObjectStreams(); |
| 496 | void generateObjectStreams(); | 496 | void generateObjectStreams(); |
| 497 | std::string getOriginalID1(); | 497 | std::string getOriginalID1(); |
| 498 | - void generateID(); | 498 | + void generateID(bool encrypted); |
| 499 | void interpretR3EncryptionParameters( | 499 | void interpretR3EncryptionParameters( |
| 500 | bool allow_accessibility, | 500 | bool allow_accessibility, |
| 501 | bool allow_extract, | 501 | bool allow_extract, |
libqpdf/QPDFJob_config.cc
| @@ -149,6 +149,9 @@ QPDFJob::Config::jpegQuality(std::string const& parameter) | @@ -149,6 +149,9 @@ QPDFJob::Config::jpegQuality(std::string const& parameter) | ||
| 149 | QPDFJob::Config* | 149 | QPDFJob::Config* |
| 150 | QPDFJob::Config::copyEncryption(std::string const& parameter) | 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 | o.m->encryption_file = parameter; | 155 | o.m->encryption_file = parameter; |
| 153 | o.m->copy_encryption = true; | 156 | o.m->copy_encryption = true; |
| 154 | o.m->encrypt = false; | 157 | o.m->encrypt = false; |
| @@ -168,6 +171,9 @@ QPDFJob::Config::decrypt() | @@ -168,6 +171,9 @@ QPDFJob::Config::decrypt() | ||
| 168 | QPDFJob::Config* | 171 | QPDFJob::Config* |
| 169 | QPDFJob::Config::deterministicId() | 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 | o.m->deterministic_id = true; | 177 | o.m->deterministic_id = true; |
| 172 | return this; | 178 | return this; |
| 173 | } | 179 | } |
| @@ -1116,6 +1122,9 @@ std::shared_ptr<QPDFJob::EncConfig> | @@ -1116,6 +1122,9 @@ std::shared_ptr<QPDFJob::EncConfig> | ||
| 1116 | QPDFJob::Config::encrypt( | 1122 | QPDFJob::Config::encrypt( |
| 1117 | int keylen, std::string const& user_password, std::string const& owner_password) | 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 | o.m->keylen = keylen; | 1128 | o.m->keylen = keylen; |
| 1120 | if (keylen == 256) { | 1129 | if (keylen == 256) { |
| 1121 | o.m->use_aes = true; | 1130 | o.m->use_aes = true; |
libqpdf/QPDFWriter.cc
| @@ -859,7 +859,7 @@ QPDFWriter::interpretR3EncryptionParameters( | @@ -859,7 +859,7 @@ QPDFWriter::interpretR3EncryptionParameters( | ||
| 859 | void | 859 | void |
| 860 | QPDFWriter::setEncryptionParameters(char const* user_password, char const* owner_password) | 860 | QPDFWriter::setEncryptionParameters(char const* user_password, char const* owner_password) |
| 861 | { | 861 | { |
| 862 | - generateID(); | 862 | + generateID(true); |
| 863 | m->encryption->setId1(m->id1); | 863 | m->encryption->setId1(m->id1); |
| 864 | m->encryption_key = m->encryption->compute_parameters(user_password, owner_password); | 864 | m->encryption_key = m->encryption->compute_parameters(user_password, owner_password); |
| 865 | setEncryptionMinimumVersion(); | 865 | setEncryptionMinimumVersion(); |
| @@ -871,7 +871,7 @@ QPDFWriter::copyEncryptionParameters(QPDF& qpdf) | @@ -871,7 +871,7 @@ QPDFWriter::copyEncryptionParameters(QPDF& qpdf) | ||
| 871 | m->preserve_encryption = false; | 871 | m->preserve_encryption = false; |
| 872 | QPDFObjectHandle trailer = qpdf.getTrailer(); | 872 | QPDFObjectHandle trailer = qpdf.getTrailer(); |
| 873 | if (trailer.hasKey("/Encrypt")) { | 873 | if (trailer.hasKey("/Encrypt")) { |
| 874 | - generateID(); | 874 | + generateID(true); |
| 875 | m->id1 = trailer.getKey("/ID").getArrayItem(0).getStringValue(); | 875 | m->id1 = trailer.getKey("/ID").getArrayItem(0).getStringValue(); |
| 876 | QPDFObjectHandle encrypt = trailer.getKey("/Encrypt"); | 876 | QPDFObjectHandle encrypt = trailer.getKey("/Encrypt"); |
| 877 | int V = encrypt.getKey("/V").getIntValueAsInt(); | 877 | int V = encrypt.getKey("/V").getIntValueAsInt(); |
| @@ -1301,7 +1301,7 @@ QPDFWriter::writeTrailer( | @@ -1301,7 +1301,7 @@ QPDFWriter::writeTrailer( | ||
| 1301 | if (linearization_pass == 0 && m->deterministic_id) { | 1301 | if (linearization_pass == 0 && m->deterministic_id) { |
| 1302 | computeDeterministicIDData(); | 1302 | computeDeterministicIDData(); |
| 1303 | } | 1303 | } |
| 1304 | - generateID(); | 1304 | + generateID(m->encryption.get()); |
| 1305 | write_string(m->id1, true).write_string(m->id2, true); | 1305 | write_string(m->id1, true).write_string(m->id2, true); |
| 1306 | } | 1306 | } |
| 1307 | write("]"); | 1307 | write("]"); |
| @@ -1865,7 +1865,7 @@ QPDFWriter::getOriginalID1() | @@ -1865,7 +1865,7 @@ QPDFWriter::getOriginalID1() | ||
| 1865 | } | 1865 | } |
| 1866 | 1866 | ||
| 1867 | void | 1867 | void |
| 1868 | -QPDFWriter::generateID() | 1868 | +QPDFWriter::generateID(bool encrypted) |
| 1869 | { | 1869 | { |
| 1870 | // Generate the ID lazily so that we can handle the user's preference to use static or | 1870 | // Generate the ID lazily so that we can handle the user's preference to use static or |
| 1871 | // deterministic ID generation. | 1871 | // deterministic ID generation. |
| @@ -1911,12 +1911,14 @@ QPDFWriter::generateID() | @@ -1911,12 +1911,14 @@ QPDFWriter::generateID() | ||
| 1911 | 1911 | ||
| 1912 | std::string seed; | 1912 | std::string seed; |
| 1913 | if (m->deterministic_id) { | 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 | throw std::runtime_error( | 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 | seed += m->deterministic_id_data; | 1923 | seed += m->deterministic_id_data; |
| 1922 | } else { | 1924 | } else { |
manual/release-notes.rst
| @@ -23,6 +23,13 @@ more detail. | @@ -23,6 +23,13 @@ more detail. | ||
| 23 | not work on some older Linux distributions. If you need support | 23 | not work on some older Linux distributions. If you need support |
| 24 | for an older distribution, please use version 12.2.0 or below. | 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 | - Other enhancements | 33 | - Other enhancements |
| 27 | 34 | ||
| 28 | - ``QPDFWriter`` will no longer add filters when writing empty streams. | 35 | - ``QPDFWriter`` will no longer add filters when writing empty streams. |
qpdf/qpdf.testcov
| @@ -254,7 +254,6 @@ QPDFJob npages 0 | @@ -254,7 +254,6 @@ QPDFJob npages 0 | ||
| 254 | QPDF already reserved object 0 | 254 | QPDF already reserved object 0 |
| 255 | QPDFWriter standard deterministic ID 1 | 255 | QPDFWriter standard deterministic ID 1 |
| 256 | QPDFWriter linearized deterministic ID 1 | 256 | QPDFWriter linearized deterministic ID 1 |
| 257 | -QPDFWriter deterministic with no data 0 | ||
| 258 | qpdf-c called qpdf_set_deterministic_ID 0 | 257 | qpdf-c called qpdf_set_deterministic_ID 0 |
| 259 | QPDFParser invalid objgen 0 | 258 | QPDFParser invalid objgen 0 |
| 260 | QPDF object id 0 0 | 259 | QPDF object id 0 0 |
qpdf/qtest/arg-parsing.test
| @@ -15,7 +15,7 @@ cleanup(); | @@ -15,7 +15,7 @@ cleanup(); | ||
| 15 | 15 | ||
| 16 | my $td = new TestDriver('arg-parsing'); | 16 | my $td = new TestDriver('arg-parsing'); |
| 17 | 17 | ||
| 18 | -my $n_tests = 26; | 18 | +my $n_tests = 30; |
| 19 | 19 | ||
| 20 | $td->runtest("required argument", | 20 | $td->runtest("required argument", |
| 21 | {$td->COMMAND => "qpdf --password minimal.pdf"}, | 21 | {$td->COMMAND => "qpdf --password minimal.pdf"}, |
| @@ -138,6 +138,26 @@ $td->runtest("missing key length", | @@ -138,6 +138,26 @@ $td->runtest("missing key length", | ||
| 138 | {$td->REGEXP => ".*encryption key length is required", | 138 | {$td->REGEXP => ".*encryption key length is required", |
| 139 | $td->EXIT_STATUS => 2}, | 139 | $td->EXIT_STATUS => 2}, |
| 140 | $td->NORMALIZE_NEWLINES); | 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 | # Disallow mixing positional and flag-style encryption arguments. | 162 | # Disallow mixing positional and flag-style encryption arguments. |
| 143 | my @bad_enc = ( | 163 | my @bad_enc = ( |
qpdf/qtest/deterministic-id.test
| @@ -54,11 +54,10 @@ foreach my $d ('nn', 'ny', 'yn', 'yy') | @@ -54,11 +54,10 @@ foreach my $d ('nn', 'ny', 'yn', 'yy') | ||
| 54 | 54 | ||
| 55 | $td->runtest("deterministic ID with encryption", | 55 | $td->runtest("deterministic ID with encryption", |
| 56 | {$td->COMMAND => "qpdf -deterministic-id encrypted-with-images.pdf a.pdf"}, | 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 | $td->NORMALIZE_NEWLINES); | 61 | $td->NORMALIZE_NEWLINES); |
| 63 | $td->runtest("deterministic ID (C API)", | 62 | $td->runtest("deterministic ID (C API)", |
| 64 | {$td->COMMAND => | 63 | {$td->COMMAND => |