Commit b46d4b9872654a953c7aedf325d36da2d2acc589

Authored by m-holger
1 parent e68d5392

Disallow `--deterministic-id` with encrypted output and improve error handling f…

…or deterministic ID generation (fixes #1235).
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 =>
... ...