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,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 =>