Changeset View
Changeset View
Standalone View
Standalone View
src/crypto/autodecryptverifyfilescontroller.cpp
| Show First 20 Lines • Show All 78 Lines • ▼ Show 20 Line(s) | 77 | public: | |||
|---|---|---|---|---|---|
| 79 | ~Private() { qCDebug(KLEOPATRA_LOG); } | 79 | ~Private() { qCDebug(KLEOPATRA_LOG); } | ||
| 80 | 80 | | |||
| 81 | void slotDialogCanceled(); | 81 | void slotDialogCanceled(); | ||
| 82 | void schedule(); | 82 | void schedule(); | ||
| 83 | 83 | | |||
| 84 | void exec(); | 84 | void exec(); | ||
| 85 | std::vector<std::shared_ptr<Task> > buildTasks(const QStringList &, QStringList &); | 85 | std::vector<std::shared_ptr<Task> > buildTasks(const QStringList &, QStringList &); | ||
| 86 | 86 | | |||
| 87 | struct CryptoFile { | ||||
| 88 | QString baseName; | ||||
| 89 | QString fileName; | ||||
| 90 | GpgME::Protocol protocol = GpgME::UnknownProtocol; | ||||
| 91 | int classification = 0; | ||||
| 92 | std::shared_ptr<Output> output; | ||||
| 93 | }; | ||||
| 94 | QVector<CryptoFile> classifyAndSortFiles(const QStringList &files); | ||||
| 95 | | ||||
| 87 | void reportError(int err, const QString &details) | 96 | void reportError(int err, const QString &details) | ||
| 88 | { | 97 | { | ||
| 89 | q->setLastError(err, details); | 98 | q->setLastError(err, details); | ||
| 90 | q->emitDoneOrError(); | 99 | q->emitDoneOrError(); | ||
| 91 | } | 100 | } | ||
| 92 | void cancelAllTasks(); | 101 | void cancelAllTasks(); | ||
| 93 | 102 | | |||
| 94 | QStringList m_passedFiles, m_filesAfterPreparation; | 103 | QStringList m_passedFiles, m_filesAfterPreparation; | ||
| ▲ Show 20 Lines • Show All 110 Lines • ▼ Show 20 Line(s) | 175 | if (m_dialog->exec() == QDialog::Accepted) { | |||
| 205 | } | 214 | } | ||
| 206 | } | 215 | } | ||
| 207 | q->emitDoneOrError(); | 216 | q->emitDoneOrError(); | ||
| 208 | delete m_dialog; | 217 | delete m_dialog; | ||
| 209 | m_dialog = nullptr; | 218 | m_dialog = nullptr; | ||
| 210 | return; | 219 | return; | ||
| 211 | } | 220 | } | ||
| 212 | 221 | | |||
| 222 | QVector<AutoDecryptVerifyFilesController::Private::CryptoFile> AutoDecryptVerifyFilesController::Private::classifyAndSortFiles(const QStringList &files) | ||||
| 223 | { | ||||
| 224 | const auto isSignature = [](int classification) -> bool { | ||||
| 225 | return mayBeDetachedSignature(classification) | ||||
| 226 | || mayBeOpaqueSignature(classification) | ||||
| 227 | || (classification & Class::TypeMask) == Class::ClearsignedMessage; | ||||
| 228 | }; | ||||
| 229 | | ||||
| 230 | QVector<CryptoFile> out; | ||||
| 231 | for (const auto &file : files) { | ||||
| 232 | CryptoFile cFile; | ||||
| 233 | cFile.fileName = file; | ||||
| 234 | cFile.baseName = file.left(file.length() - 4); | ||||
| 235 | cFile.classification = classify(file); | ||||
| 236 | cFile.protocol = findProtocol(cFile.classification); | ||||
| 237 | | ||||
| 238 | auto it = std::find_if(out.begin(), out.end(), | ||||
| 239 | [&cFile](const CryptoFile &other) { | ||||
| 240 | return other.protocol == cFile.protocol | ||||
| 241 | && other.baseName == cFile.baseName; | ||||
| 242 | }); | ||||
| 243 | if (it != out.end()) { | ||||
| 244 | // If we found a file with the same basename, make sure that encrypted | ||||
| 245 | // file is before the signature file, so that we first decrypt and then | ||||
| 246 | // verify | ||||
| 247 | if (isSignature(cFile.classification) && isCipherText(it->classification)) { | ||||
| 248 | out.insert(it + 1, cFile); | ||||
| 249 | } else if (isCipherText(cFile.classification) && isSignature(it->classification)) { | ||||
| 250 | out.insert(it, cFile); | ||||
| 251 | } else { | ||||
| 252 | // both are signatures or both are encrypted files, in which | ||||
| 253 | // case order does not matter | ||||
| 254 | out.insert(it, cFile); | ||||
| 255 | } | ||||
| 256 | } else { | ||||
| 257 | out.push_back(cFile); | ||||
| 258 | } | ||||
| 259 | } | ||||
| 260 | | ||||
| 261 | return out; | ||||
| 262 | } | ||||
| 263 | | ||||
| 264 | | ||||
| 213 | std::vector< std::shared_ptr<Task> > AutoDecryptVerifyFilesController::Private::buildTasks(const QStringList &fileNames, QStringList &undetected) | 265 | std::vector< std::shared_ptr<Task> > AutoDecryptVerifyFilesController::Private::buildTasks(const QStringList &fileNames, QStringList &undetected) | ||
| 214 | { | 266 | { | ||
| 215 | std::vector<std::shared_ptr<Task> > tasks; | 267 | // sort files so that we make sure we first decrypt and then verify | ||
| 216 | for (const QString &fName : fileNames) { | 268 | QVector<CryptoFile> cryptoFiles = classifyAndSortFiles(fileNames); | ||
| 217 | const auto classification = classify(fName); | | |||
| 218 | const auto proto = findProtocol(classification); | | |||
| 219 | 269 | | |||
| 220 | QFileInfo fi(fName); | 270 | std::vector<std::shared_ptr<Task> > tasks; | ||
| 221 | qCDebug(KLEOPATRA_LOG) << "classified" << fName << "as" << printableClassification(classification); | 271 | for (auto it = cryptoFiles.begin(), end = cryptoFiles.end(); it != end; ++it) { | ||
| 272 | auto &cFile = (*it); | ||||
| 273 | QFileInfo fi(cFile.fileName); | ||||
| 274 | qCDebug(KLEOPATRA_LOG) << "classified" << cFile.fileName << "as" << printableClassification(cFile.classification); | ||||
| 222 | 275 | | |||
| 223 | if (!fi.isReadable()) { | 276 | if (!fi.isReadable()) { | ||
| 224 | reportError(makeGnuPGError(GPG_ERR_ASS_NO_INPUT), | 277 | reportError(makeGnuPGError(GPG_ERR_ASS_NO_INPUT), | ||
| 225 | xi18n("Cannot open <filename>%1</filename> for reading.", fName)); | 278 | xi18n("Cannot open <filename>%1</filename> for reading.", cFile.fileName)); | ||
| 226 | } else if (mayBeAnyCertStoreType(classification)) { | 279 | continue; | ||
| 280 | } | ||||
| 281 | | ||||
| 282 | if (mayBeAnyCertStoreType(cFile.classification)) { | ||||
| 227 | // Trying to verify a certificate. Possible because extensions are often similar | 283 | // Trying to verify a certificate. Possible because extensions are often similar | ||
| 228 | // for PGP Keys. | 284 | // for PGP Keys. | ||
| 229 | reportError(makeGnuPGError(GPG_ERR_ASS_NO_INPUT), | 285 | reportError(makeGnuPGError(GPG_ERR_ASS_NO_INPUT), | ||
| 230 | xi18n("The file <filename>%1</filename> contains certificates and can't be decrypted or verified.", fName)); | 286 | xi18n("The file <filename>%1</filename> contains certificates and can't be decrypted or verified.", cFile.fileName)); | ||
| 231 | qCDebug(KLEOPATRA_LOG) << "reported error"; | 287 | qCDebug(KLEOPATRA_LOG) << "reported error"; | ||
| 232 | } else if (isDetachedSignature(classification)) { | 288 | continue; | ||
| 289 | } | ||||
| 290 | | ||||
| 291 | // We can't reliably detect CMS detached signatures, so we will try to do | ||||
| 292 | // our best to use the current file as a detached signature and fallback to | ||||
| 293 | // opaque signature otherwise. | ||||
| 294 | if (cFile.protocol == GpgME::CMS && mayBeDetachedSignature(cFile.classification)) { | ||||
| 295 | // First, see if previous task was a decryption task for the same file | ||||
| 296 | // and "pipe" it's output into our input | ||||
| 297 | std::shared_ptr<Input> input; | ||||
| 298 | bool prepend = false; | ||||
| 299 | if (it != cryptoFiles.begin()) { | ||||
| 300 | const auto prev = it - 1; | ||||
| 301 | if (prev->protocol == cFile.protocol && prev->baseName == cFile.baseName) { | ||||
| 302 | input = Input::createFromOutput(prev->output); | ||||
| 303 | prepend = true; | ||||
| 304 | } | ||||
| 305 | } | ||||
| 306 | | ||||
| 307 | if (!input) { | ||||
| 308 | if (QFile::exists(cFile.baseName)) { | ||||
| 309 | input = Input::createFromFile(cFile.baseName); | ||||
| 310 | } | ||||
| 311 | } | ||||
| 312 | | ||||
| 313 | if (input) { | ||||
| 314 | qCDebug(KLEOPATRA_LOG) << "Detached CMS verify: " << cFile.fileName; | ||||
| 315 | std::shared_ptr<VerifyDetachedTask> t(new VerifyDetachedTask); | ||||
| 316 | t->setInput(Input::createFromFile(cFile.fileName)); | ||||
| 317 | t->setSignedData(input); | ||||
| 318 | t->setProtocol(cFile.protocol); | ||||
| 319 | if (prepend) { | ||||
| 320 | // Put the verify task BEFORE the decrypt task in the tasks queue, | ||||
| 321 | // because the tasks are executed in reverse order! | ||||
| 322 | tasks.insert(tasks.end() - 1, t); | ||||
| 323 | } else { | ||||
| 324 | tasks.push_back(t); | ||||
| 325 | } | ||||
| 326 | continue; | ||||
| 327 | } else { | ||||
| 328 | // No signed data, maybe not a detached signature | ||||
| 329 | } | ||||
| 330 | } | ||||
| 331 | | ||||
| 332 | if (isDetachedSignature(cFile.classification)) { | ||||
| 233 | // Detached signature, try to find data or ask the user. | 333 | // Detached signature, try to find data or ask the user. | ||
| 234 | QString signedDataFileName = findSignedData(fName); | 334 | QString signedDataFileName = cFile.baseName; | ||
| 235 | if (signedDataFileName.isEmpty()) { | 335 | if (signedDataFileName.isEmpty()) { | ||
| 236 | signedDataFileName = QFileDialog::getOpenFileName(nullptr, xi18n("Select the file to verify with \"%1\"", fi.fileName()), | 336 | signedDataFileName = QFileDialog::getOpenFileName(nullptr, xi18n("Select the file to verify with \"%1\"", fi.fileName()), | ||
| 237 | fi.dir().dirName()); | 337 | fi.dir().dirName()); | ||
| 238 | } | 338 | } | ||
| 239 | if (signedDataFileName.isEmpty()) { | 339 | if (signedDataFileName.isEmpty()) { | ||
| 240 | qCDebug(KLEOPATRA_LOG) << "No signed data selected. Verify abortet."; | 340 | qCDebug(KLEOPATRA_LOG) << "No signed data selected. Verify abortet."; | ||
| 241 | continue; | 341 | } else { | ||
| 242 | } | 342 | qCDebug(KLEOPATRA_LOG) << "Detached verify: " << cFile.fileName << " Data: " << signedDataFileName; | ||
| 243 | qCDebug(KLEOPATRA_LOG) << "Detached verify: " << fName << " Data: " << signedDataFileName; | | |||
| 244 | std::shared_ptr<VerifyDetachedTask> t(new VerifyDetachedTask); | 343 | std::shared_ptr<VerifyDetachedTask> t(new VerifyDetachedTask); | ||
| 245 | t->setInput(Input::createFromFile(fName)); | 344 | t->setInput(Input::createFromFile(cFile.fileName)); | ||
| 246 | t->setSignedData(Input::createFromFile(signedDataFileName)); | 345 | t->setSignedData(Input::createFromFile(signedDataFileName)); | ||
| 247 | t->setProtocol(proto); | 346 | t->setProtocol(cFile.protocol); | ||
| 248 | tasks.push_back(t); | 347 | tasks.push_back(t); | ||
| 249 | } else if (!mayBeAnyMessageType(classification)) { | 348 | } | ||
| 349 | continue; | ||||
| 350 | } | ||||
| 351 | | ||||
| 352 | if (!mayBeAnyMessageType(cFile.classification)) { | ||||
| 250 | // Not a Message? Maybe there is a signature for this file? | 353 | // Not a Message? Maybe there is a signature for this file? | ||
| 251 | const auto signatures = findSignatures(fName); | 354 | const auto signatures = findSignatures(cFile.fileName); | ||
| 252 | if (!signatures.empty()) { | 355 | if (!signatures.empty()) { | ||
| 253 | for (const QString &sig : signatures) { | 356 | for (const QString &sig : signatures) { | ||
| 254 | qCDebug(KLEOPATRA_LOG) << "Guessing: " << sig << " is a signature for: " << fName; | 357 | qCDebug(KLEOPATRA_LOG) << "Guessing: " << sig << " is a signature for: " << cFile.fileName; | ||
| 255 | std::shared_ptr<VerifyDetachedTask> t(new VerifyDetachedTask); | 358 | std::shared_ptr<VerifyDetachedTask> t(new VerifyDetachedTask); | ||
| 256 | t->setInput(Input::createFromFile(sig)); | 359 | t->setInput(Input::createFromFile(sig)); | ||
| 257 | t->setSignedData(Input::createFromFile(fName)); | 360 | t->setSignedData(Input::createFromFile(cFile.fileName)); | ||
| 258 | t->setProtocol(proto); | 361 | t->setProtocol(cFile.protocol); | ||
| 259 | tasks.push_back(t); | 362 | tasks.push_back(t); | ||
| 260 | } | 363 | } | ||
| 261 | } else { | 364 | } else { | ||
| 262 | undetected << fName; | 365 | undetected << cFile.fileName; | ||
| 263 | qCDebug(KLEOPATRA_LOG) << "Failed detection for: " << fName << " adding to undetected."; | 366 | qCDebug(KLEOPATRA_LOG) << "Failed detection for: " << cFile.fileName << " adding to undetected."; | ||
| 264 | } | 367 | } | ||
| 265 | } else { | 368 | } else { | ||
| 266 | // Any Message type so we have input and output. | 369 | // Any Message type so we have input and output. | ||
| 267 | const auto input = Input::createFromFile(fName); | 370 | const auto input = Input::createFromFile(cFile.fileName); | ||
| 268 | const auto archiveDefinitions = ArchiveDefinition::getArchiveDefinitions(); | 371 | const auto archiveDefinitions = ArchiveDefinition::getArchiveDefinitions(); | ||
| 269 | 372 | | |||
| 270 | const auto ad = q->pick_archive_definition(proto, archiveDefinitions, fName); | 373 | const auto ad = q->pick_archive_definition(cFile.protocol, archiveDefinitions, cFile.fileName); | ||
| 271 | 374 | | |||
| 272 | const auto wd = QDir(m_workDir.path()); | 375 | const auto wd = QDir(m_workDir.path()); | ||
| 273 | 376 | | |||
| 274 | const auto output = | 377 | const auto output = | ||
| 275 | ad ? ad->createOutputFromUnpackCommand(proto, fName, wd) : | 378 | ad ? ad->createOutputFromUnpackCommand(cFile.protocol, cFile.fileName, wd) : | ||
| 276 | /*else*/ Output::createFromFile(wd.absoluteFilePath(outputFileName(fi.fileName())), false); | 379 | /*else*/ Output::createFromFile(wd.absoluteFilePath(outputFileName(fi.fileName())), false); | ||
| 277 | 380 | | |||
| 278 | if (isOpaqueSignature(classification)) { | 381 | // If this might be opaque CMS signature, then try that. We already handled | ||
| 382 | // detached CMS signature above | ||||
| 383 | const auto isCMSOpaqueSignature = cFile.protocol == GpgME::CMS && mayBeOpaqueSignature(cFile.classification); | ||||
| 384 | | ||||
| 385 | if (isOpaqueSignature(cFile.classification) || isCMSOpaqueSignature) { | ||||
| 279 | qCDebug(KLEOPATRA_LOG) << "creating a VerifyOpaqueTask"; | 386 | qCDebug(KLEOPATRA_LOG) << "creating a VerifyOpaqueTask"; | ||
| 280 | std::shared_ptr<VerifyOpaqueTask> t(new VerifyOpaqueTask); | 387 | std::shared_ptr<VerifyOpaqueTask> t(new VerifyOpaqueTask); | ||
| 281 | t->setInput(input); | 388 | t->setInput(input); | ||
| 282 | t->setOutput(output); | 389 | t->setOutput(output); | ||
| 283 | t->setProtocol(proto); | 390 | t->setProtocol(cFile.protocol); | ||
| 284 | tasks.push_back(t); | 391 | tasks.push_back(t); | ||
| 285 | } else { | 392 | } else { | ||
| 286 | // Any message. That is not an opaque signature needs to be | 393 | // Any message. That is not an opaque signature needs to be | ||
| 287 | // decrypted. Verify we always do because we can't know if | 394 | // decrypted. Verify we always do because we can't know if | ||
| 288 | // an encrypted message is also signed. | 395 | // an encrypted message is also signed. | ||
| 289 | qCDebug(KLEOPATRA_LOG) << "creating a DecryptVerifyTask"; | 396 | qCDebug(KLEOPATRA_LOG) << "creating a DecryptVerifyTask"; | ||
| 290 | std::shared_ptr<DecryptVerifyTask> t(new DecryptVerifyTask); | 397 | std::shared_ptr<DecryptVerifyTask> t(new DecryptVerifyTask); | ||
| 291 | t->setInput(input); | 398 | t->setInput(input); | ||
| 292 | t->setOutput(output); | 399 | t->setOutput(output); | ||
| 293 | t->setProtocol(proto); | 400 | t->setProtocol(cFile.protocol); | ||
| 401 | cFile.output = output; | ||||
| 294 | tasks.push_back(t); | 402 | tasks.push_back(t); | ||
| 295 | } | 403 | } | ||
| 296 | } | 404 | } | ||
| 297 | } | 405 | } | ||
| 298 | 406 | | |||
| 299 | return tasks; | 407 | return tasks; | ||
| 300 | } | 408 | } | ||
| 301 | 409 | | |||
| ▲ Show 20 Lines • Show All 82 Lines • Show Last 20 Lines | |||||