1 #include "decryption_state_machine.h" 2 #include <limits> 3 #include "state_graph.h" 4 #include "state_machine.h" 5 #include "encryptmsg/emsg_exception.h" 6 #include "decryption_state_debug.h" 7 #include "x2_key_loader.h" 8 #include "key_file_converter.h" 9 #include "wad_reader_writer.h" 10 #include "epad_result.h" 11 #include "encryptmsg/openpgp_conversions.h" 12 13 using namespace LightStateMachine; 14 using namespace EncryptMsg; 15 using namespace EncryptPad; 16 17 namespace EncryptPad 18 { 19 20 struct DecryptionStateMachine : public LightStateMachine::NonCopyable 21 { 22 int filter_count_; 23 Format format_; 24 EpadResult result_; 25 bool is_wad_head_finished_; 26 InStream &in_; 27 OutStream &out_; 28 const EncryptParams &encrypt_params_; 29 PacketMetadata &metadata_; 30 EncryptMsg::SafeVector buffer_; 31 EncryptMsg::SafeVector pending_buffer_; 32 std::unique_ptr<DecryptionSession> passphrase_session_; 33 std::unique_ptr<DecryptionSession> key_file_session_; 34 ProgressEvent progress_event_; 35 36 StateGraph graph_; 37 StateMachineContext context_; 38 StateMachine state_machine_; 39 40 bool ReadIn_CanEnter(StateMachineContext &ctx); 41 bool ParseFormat_CanEnter(StateMachineContext &ctx); 42 bool SetPwdKey_CanEnter(StateMachineContext &ctx); 43 bool ReadKeyFile_CanEnter(StateMachineContext &ctx); 44 bool GPG_CanEnter(StateMachineContext &ctx); 45 bool WADHead_CanEnter(StateMachineContext &ctx); 46 bool WriteOut_CanEnter(StateMachineContext &ctx); 47 bool End_CanEnter(StateMachineContext &ctx); 48 49 void ReadIn_OnEnter(StateMachineContext &ctx); 50 void ParseFormat_OnEnter(StateMachineContext &ctx); 51 void SetPwdKey_OnEnter(StateMachineContext &ctx); 52 void ReadKeyFile_OnEnter(StateMachineContext &ctx); 53 void GPG_OnEnter(StateMachineContext &ctx); 54 void WADHead_OnEnter(StateMachineContext &ctx); 55 void WriteOut_OnEnter(StateMachineContext &ctx); 56 void Fail_OnEnter(StateMachineContext &ctx); 57 void End_OnEnter(StateMachineContext &ctx); 58 59 bool IsEncryptedEmptyString(); 60 61 DecryptionStateMachine(InStream &in, OutStream &out, 62 const EncryptParams &encrypt_params, PacketMetadata &metadata); 63 void DecryptStream(); 64 }; 65 DecryptionStateMachine(InStream & in,OutStream & out,const EncryptParams & encrypt_params,PacketMetadata & metadata)66 DecryptionStateMachine::DecryptionStateMachine(InStream &in, OutStream &out, 67 const EncryptParams &encrypt_params, PacketMetadata &metadata): 68 filter_count_(0), 69 format_(Format::Unknown), 70 result_(EpadResult::None), 71 is_wad_head_finished_(false), 72 in_(in), 73 out_(out), 74 encrypt_params_(encrypt_params), 75 metadata_(metadata), 76 progress_event_(in_.GetCount(), 0), 77 state_machine_(graph_, context_) 78 { 79 using Self = DecryptionStateMachine; 80 using VoidF = LightStateMachine::VoidMemberFunction<DecryptionStateMachine>; 81 using BoolF = LightStateMachine::BoolMemberFunction<DecryptionStateMachine>; 82 83 // Start state 84 85 graph_.Create(StateID::Start); 86 87 // Fail state 88 89 graph_.Create(StateID::Fail, 90 VoidF(this, &Self::Fail_OnEnter), StubVoidFunction, 91 StubBoolFunction, AlwaysFalseBoolFunction); 92 93 graph_.SetStartStateID(StateID::Start); 94 graph_.SetFailStateID(StateID::Fail); 95 96 // Other states 97 98 graph_.Create(StateID::ReadIn, 99 VoidF(this, &Self::ReadIn_OnEnter), StubVoidFunction, 100 BoolF(this, &Self::ReadIn_CanEnter), StubBoolFunction); 101 102 graph_.Create(StateID::ParseFormat, 103 VoidF(this, &Self::ParseFormat_OnEnter), StubVoidFunction, 104 BoolF(this, &Self::ParseFormat_CanEnter), StubBoolFunction); 105 106 graph_.Create(StateID::SetPwdKey, 107 VoidF(this, &Self::SetPwdKey_OnEnter), StubVoidFunction, 108 BoolF(this, &Self::SetPwdKey_CanEnter), StubBoolFunction); 109 110 graph_.Create(StateID::ReadKeyFile, 111 VoidF(this, &Self::ReadKeyFile_OnEnter), StubVoidFunction, 112 BoolF(this, &Self::ReadKeyFile_CanEnter), StubBoolFunction); 113 114 graph_.Create(StateID::GPG, 115 VoidF(this, &Self::GPG_OnEnter), StubVoidFunction, 116 BoolF(this, &Self::GPG_CanEnter), StubBoolFunction); 117 118 graph_.Create(StateID::WADHead, 119 VoidF(this, &Self::WADHead_OnEnter), StubVoidFunction, 120 BoolF(this, &Self::WADHead_CanEnter), StubBoolFunction); 121 122 graph_.Create(StateID::WriteOut, 123 VoidF(this, &Self::WriteOut_OnEnter), StubVoidFunction, 124 BoolF(this, &Self::WriteOut_CanEnter), StubBoolFunction); 125 126 graph_.Create(StateID::End, 127 VoidF(this, &Self::End_OnEnter), StubVoidFunction, 128 BoolF(this, &Self::End_CanEnter), AlwaysFalseBoolFunction); 129 130 131 graph_.Link(StateID::Start, StateID::ReadIn); 132 graph_.Link(StateID::ReadIn, StateID::End); 133 graph_.Link(StateID::ReadIn, StateID::ParseFormat); 134 graph_.Link(StateID::ReadIn, StateID::GPG); 135 graph_.Link(StateID::ReadIn, StateID::WADHead); 136 137 graph_.Link(StateID::ParseFormat, StateID::ReadIn); 138 graph_.Link(StateID::ParseFormat, StateID::GPG); 139 graph_.Link(StateID::ParseFormat, StateID::WADHead); 140 graph_.Link(StateID::ParseFormat, StateID::ReadKeyFile); 141 graph_.Link(StateID::ParseFormat, StateID::SetPwdKey); 142 graph_.Link(StateID::ParseFormat, StateID::WriteOut); 143 144 graph_.Link(StateID::SetPwdKey, StateID::GPG); 145 146 graph_.Link(StateID::ReadKeyFile, StateID::ReadIn); 147 graph_.Link(StateID::ReadKeyFile, StateID::GPG); 148 149 graph_.Link(StateID::GPG, StateID::ParseFormat); 150 graph_.Link(StateID::GPG, StateID::ReadIn); 151 graph_.Link(StateID::GPG, StateID::WADHead); 152 graph_.Link(StateID::GPG, StateID::GPG); 153 graph_.Link(StateID::GPG, StateID::WriteOut); 154 155 graph_.Link(StateID::WADHead, StateID::ReadKeyFile); 156 graph_.Link(StateID::WADHead, StateID::GPG); 157 graph_.Link(StateID::WADHead, StateID::ReadIn); 158 159 graph_.Link(StateID::WriteOut, StateID::ReadIn); 160 161 state_machine_.SetStateIDToStringConverter( 162 [](StateMachineStateID id){ 163 return std::string("decryption: ") + 164 PrintDecryptionStateMachineStateID(id);}); 165 } 166 DecryptStream()167 void DecryptionStateMachine::DecryptStream() 168 { 169 while(state_machine_.NextState()) 170 { 171 } 172 } 173 DecryptStream(InStream & in,const EncryptParams & encrypt_params,OutStream & out,PacketMetadata & metadata)174 EpadResult DecryptStream(InStream &in, const EncryptParams &encrypt_params, 175 OutStream &out, PacketMetadata &metadata) 176 { 177 DecryptionStateMachine state_machine(in, out, encrypt_params, metadata); 178 state_machine.DecryptStream(); 179 return state_machine.result_; 180 } 181 ReadIn_CanEnter(LightStateMachine::StateMachineContext & ctx)182 bool DecryptionStateMachine::ReadIn_CanEnter(LightStateMachine::StateMachineContext &ctx) 183 { 184 return buffer_.size() == 0; 185 } 186 ReadIn_OnEnter(LightStateMachine::StateMachineContext & ctx)187 void DecryptionStateMachine::ReadIn_OnEnter(LightStateMachine::StateMachineContext &ctx) 188 { 189 //memory_buffer should be smaller than max sizes of both types 190 assert(static_cast<uint64_t>(encrypt_params_.memory_buffer) <= 191 std::min( 192 static_cast<uint64_t>(std::numeric_limits<stream_length_type>::max()), 193 static_cast<uint64_t>(std::numeric_limits<size_t>::max()) 194 )); 195 stream_length_type size2read = std::min(static_cast<stream_length_type>(encrypt_params_.memory_buffer), in_.GetCount()); 196 buffer_.resize(static_cast<size_t>(size2read)); 197 size_t size = in_.Read(buffer_.data(), buffer_.size()); 198 buffer_.resize(size); 199 filter_count_ = 0; 200 encrypt_params_.progress_callback(progress_event_); 201 if(progress_event_.cancel) 202 { 203 result_ = EpadResult::Cancelled; 204 ctx.SetFailed(true); 205 } 206 progress_event_.complete_bytes += size; 207 208 } 209 End_CanEnter(LightStateMachine::StateMachineContext & ctx)210 bool DecryptionStateMachine::End_CanEnter(LightStateMachine::StateMachineContext &ctx) 211 { 212 return in_.IsEOF() && buffer_.empty() && pending_buffer_.empty() 213 && result_ == EpadResult::Success; 214 } 215 End_OnEnter(LightStateMachine::StateMachineContext & ctx)216 void DecryptionStateMachine::End_OnEnter(LightStateMachine::StateMachineContext &ctx) 217 { 218 // Take outer reader 219 MessageReader *reader = nullptr; 220 switch(format_) 221 { 222 case Format::NestedWAD: 223 case Format::GPG: 224 reader = &passphrase_session_->reader; 225 break; 226 227 case Format::GPGByKeyFile: 228 case Format::WAD: 229 reader = &key_file_session_->reader; 230 break; 231 232 default: 233 result_ = EpadResult::UnexpectedError; 234 ctx.SetFailed(true); 235 return; 236 } 237 auto &metadata = metadata_; 238 auto &config = reader->GetMessageConfig(); 239 240 metadata.file_name = config.GetFileName(); 241 metadata.file_date = config.GetFileDate(); 242 metadata.is_binary = config.GetBinary(); 243 metadata.cipher_algo = config.GetCipherAlgo(); 244 metadata.compression = config.GetCompression(); 245 metadata.hash_algo = config.GetHashAlgo(); 246 metadata.iterations = DecodeS2KIterations(config.GetIterations()); 247 metadata.salt = reader->GetSalt(); 248 249 result_ = EpadResult::Success; 250 } 251 ParseFormat_CanEnter(LightStateMachine::StateMachineContext & ctx)252 bool DecryptionStateMachine::ParseFormat_CanEnter(LightStateMachine::StateMachineContext &ctx) 253 { 254 switch(format_) 255 { 256 case Format::Unknown: 257 break; 258 case Format::GPGOrNestedWad: 259 if(filter_count_ != 1) 260 return false; 261 break; 262 default: 263 return false; 264 } 265 266 return buffer_.size() > 0 || pending_buffer_.size() > 0 || IsEncryptedEmptyString(); 267 } 268 ParseFormat_OnEnter(LightStateMachine::StateMachineContext & ctx)269 void DecryptionStateMachine::ParseFormat_OnEnter(LightStateMachine::StateMachineContext &ctx) 270 { 271 size_t required_bytes = filter_count_ == 1 ? 4 : 1; 272 273 if(IsEncryptedEmptyString()) 274 { 275 format_ = Format::GPG; 276 return; 277 } 278 279 pending_buffer_.insert(pending_buffer_.end(), buffer_.begin(), buffer_.end()); 280 buffer_.clear(); 281 282 // We need more bytes 283 if(pending_buffer_.size() < required_bytes && !in_.IsEOF()) 284 return; 285 286 if(filter_count_ == 0) 287 { 288 uint8_t b = pending_buffer_[0]; 289 if((b & 0x80 && b != 0xEF) || b == '-') 290 { 291 if(metadata_.key_only) 292 { 293 format_ = Format::GPGByKeyFile; 294 } 295 else 296 { 297 format_ = Format::GPGOrNestedWad; 298 } 299 } 300 else // wad starts from I or P, in which the most significant bit is not set 301 { 302 format_ = Format::WAD; 303 } 304 } 305 else if(filter_count_ == 1) 306 { 307 std::string marker; 308 309 if(pending_buffer_.size() >= 4) 310 { 311 marker.insert(0U, reinterpret_cast<const char*>(pending_buffer_.data()), 4U); 312 } 313 314 if(marker == "IWAD" || marker == "PWAD") 315 { 316 format_ = Format::NestedWAD; 317 } 318 else 319 { 320 format_ = Format::GPG; 321 } 322 } 323 else 324 { 325 // Filter count can be only 0 or 1 326 assert(false); 327 } 328 329 buffer_.swap(pending_buffer_); 330 } 331 GPG_CanEnter(LightStateMachine::StateMachineContext & ctx)332 bool DecryptionStateMachine::GPG_CanEnter(LightStateMachine::StateMachineContext &ctx) 333 { 334 if(filter_count_ > 1) 335 return false; 336 337 switch(format_) 338 { 339 case Format::Empty: 340 case Format::Unknown: 341 return false; 342 343 case Format::GPG: 344 case Format::GPGOrNestedWad: 345 if(!passphrase_session_) 346 return false; 347 if(filter_count_ == 1) 348 return false; 349 break; 350 351 case Format::GPGByKeyFile: 352 if(!key_file_session_) 353 return false; 354 if(filter_count_ == 1) 355 return false; 356 break; 357 358 case Format::WAD: 359 if(!is_wad_head_finished_) 360 return false; 361 if(!key_file_session_) 362 return false; 363 if(filter_count_ == 1) 364 return false; 365 break; 366 367 case Format::NestedWAD: 368 assert(passphrase_session_); 369 if(filter_count_ == 1 && !is_wad_head_finished_) 370 return false; 371 if(filter_count_ == 1 && !key_file_session_) 372 return false; 373 break; 374 375 default: 376 break; 377 378 } 379 380 if(buffer_.size() == 0) 381 return false; 382 383 return true; 384 } 385 GPG_OnEnter(LightStateMachine::StateMachineContext & ctx)386 void DecryptionStateMachine::GPG_OnEnter(LightStateMachine::StateMachineContext &ctx) 387 { 388 using namespace EncryptMsg; 389 MessageReader *reader = nullptr; 390 bool is_key_file_session = false; 391 switch(format_) 392 { 393 case Format::GPG: 394 case Format::GPGOrNestedWad: 395 assert(filter_count_ == 0); 396 reader = &passphrase_session_->reader; 397 break; 398 399 case Format::NestedWAD: 400 is_key_file_session = (filter_count_ > 0); 401 reader = (!is_key_file_session) 402 ? 403 &passphrase_session_->reader 404 : 405 &key_file_session_->reader; 406 break; 407 408 default: 409 is_key_file_session = true; 410 reader = &key_file_session_->reader; 411 break; 412 } 413 414 try 415 { 416 if(in_.IsEOF()) 417 { 418 reader->Finish(buffer_); 419 } 420 else 421 { 422 reader->Update(buffer_); 423 } 424 } 425 catch(const EmsgException &e) 426 { 427 result_ = ToEpadResult(e.result); 428 if(is_key_file_session && (result_ == EpadResult::InvalidSurrogateIV || 429 result_ == EpadResult::MDCError)) 430 { 431 result_ = EpadResult::InvalidKeyFile; 432 } 433 ctx.SetFailed(true); 434 return; 435 } 436 filter_count_ ++; 437 result_ = EpadResult::Success; 438 } 439 SetPwdKey_CanEnter(LightStateMachine::StateMachineContext & ctx)440 bool DecryptionStateMachine::SetPwdKey_CanEnter(LightStateMachine::StateMachineContext &ctx) 441 { 442 switch(format_) 443 { 444 case Format::GPG: 445 case Format::GPGOrNestedWad: 446 if(!passphrase_session_) 447 return true; 448 break; 449 450 default: 451 break; 452 } 453 return false; 454 } 455 SetPwdKey_OnEnter(LightStateMachine::StateMachineContext & ctx)456 void DecryptionStateMachine::SetPwdKey_OnEnter(LightStateMachine::StateMachineContext &ctx) 457 { 458 passphrase_session_.reset( 459 new DecryptionSession( 460 encrypt_params_.key_service, 461 encrypt_params_.passphrase) 462 ); 463 } 464 WriteOut_CanEnter(LightStateMachine::StateMachineContext & ctx)465 bool DecryptionStateMachine::WriteOut_CanEnter(LightStateMachine::StateMachineContext &ctx) 466 { 467 if(buffer_.size() == 0) 468 return false; 469 470 switch(format_) 471 { 472 case Format::Empty: 473 case Format::Unknown: 474 case Format::GPGOrNestedWad: 475 return false; 476 477 case Format::GPGByKeyFile: 478 case Format::GPG: 479 case Format::WAD: 480 if(filter_count_ != 1) 481 return false; 482 break; 483 484 case Format::NestedWAD: 485 if(filter_count_ != 2) 486 return false; 487 break; 488 489 default: 490 break; 491 } 492 return true; 493 } 494 WriteOut_OnEnter(LightStateMachine::StateMachineContext & ctx)495 void DecryptionStateMachine::WriteOut_OnEnter(LightStateMachine::StateMachineContext &ctx) 496 { 497 out_.Write(buffer_.data(), buffer_.size()); 498 buffer_.clear(); 499 } 500 Fail_OnEnter(LightStateMachine::StateMachineContext & ctx)501 void DecryptionStateMachine::Fail_OnEnter(LightStateMachine::StateMachineContext &ctx) 502 { 503 // If we came here because no states can enter 504 if(result_ == EpadResult::Success) 505 result_ = EpadResult::UnexpectedError; 506 } 507 ReadKeyFile_CanEnter(LightStateMachine::StateMachineContext & ctx)508 bool DecryptionStateMachine::ReadKeyFile_CanEnter(LightStateMachine::StateMachineContext &ctx) 509 { 510 if(key_file_session_) 511 return false; 512 513 switch(format_) 514 { 515 case Format::GPGByKeyFile: 516 break; 517 518 case Format::WAD: 519 case Format::NestedWAD: 520 if(!is_wad_head_finished_) 521 return false; 522 break; 523 524 default: 525 return false; 526 } 527 return true; 528 } 529 ReadKeyFile_OnEnter(LightStateMachine::StateMachineContext & ctx)530 void DecryptionStateMachine::ReadKeyFile_OnEnter(LightStateMachine::StateMachineContext &ctx) 531 { 532 PacketMetadata &metadata = metadata_; 533 534 if(metadata.key_file.empty()) 535 { 536 result_ = EpadResult::KeyFileNotSpecified; 537 ctx.SetFailed(true); 538 return; 539 } 540 541 key_file_session_.reset(new DecryptionSession()); 542 543 std::string empty_str; 544 EpadResult result = LoadKeyFromFile(metadata.key_file, 545 encrypt_params_.libcurl_path ? *encrypt_params_.libcurl_path : empty_str, 546 encrypt_params_.libcurl_parameters ? *encrypt_params_.libcurl_parameters : empty_str, 547 key_file_session_->own_passphrase); 548 549 if(result != EpadResult::Success) 550 { 551 result_ = result; 552 ctx.SetFailed(true); 553 return; 554 } 555 556 if(!DecryptKeyFileContent(key_file_session_->own_passphrase, 557 encrypt_params_.key_file_encrypt_params, key_file_session_->own_passphrase)) 558 { 559 result_ = EpadResult::InvalidKeyFilePassphrase; 560 ctx.SetFailed(true); 561 return; 562 } 563 564 if(!ValidateDecryptedKeyFile(key_file_session_->own_passphrase)) 565 { 566 result_ = EpadResult::InvalidKeyFile; 567 ctx.SetFailed(true); 568 return; 569 } 570 result_ = EpadResult::Success; 571 } 572 WADHead_CanEnter(LightStateMachine::StateMachineContext & ctx)573 bool DecryptionStateMachine::WADHead_CanEnter(LightStateMachine::StateMachineContext &ctx) 574 { 575 if(is_wad_head_finished_) 576 return false; 577 578 if(buffer_.size() == 0 && pending_buffer_.size() == 0) 579 return false; 580 581 switch(format_) 582 { 583 case Format::WAD: 584 case Format::NestedWAD: 585 break; 586 587 default: 588 return false; 589 } 590 return true; 591 } 592 WADHead_OnEnter(LightStateMachine::StateMachineContext & ctx)593 void DecryptionStateMachine::WADHead_OnEnter(LightStateMachine::StateMachineContext &ctx) 594 { 595 pending_buffer_.insert(pending_buffer_.end(), buffer_.begin(), buffer_.end()); 596 buffer_.clear(); 597 598 InPacketStreamMemory stm_in(pending_buffer_.data(), 599 pending_buffer_.data() + pending_buffer_.size()); 600 601 uint32_t payload_offset = 0; 602 uint32_t payload_size = 0; 603 std::string key_file; 604 EpadResult result = ParseWad(stm_in, key_file, payload_offset, payload_size); 605 606 switch(result) 607 { 608 case EpadResult::Success: 609 break; 610 611 case EpadResult::InvalidOrIncompleteWadFile: 612 if(in_.IsEOF()) 613 { 614 result_ = result; 615 ctx.SetFailed(true); 616 } 617 return; 618 619 default: 620 result_ = result; 621 ctx.SetFailed(true); 622 return; 623 } 624 625 if(metadata_.key_file.empty()) 626 metadata_.key_file = key_file; 627 628 buffer_.swap(pending_buffer_); 629 buffer_.erase(buffer_.begin(), buffer_.begin() + payload_offset); 630 if(payload_size != 0 && payload_size < buffer_.size()) 631 { 632 //This is 3.2.1 format, in which the key string and the dictionary goes after the payload. 633 //We need to trim the file 634 buffer_.erase(buffer_.begin() + payload_size, buffer_.end()); 635 } 636 is_wad_head_finished_ = true; 637 result_ = EpadResult::Success; 638 } 639 IsEncryptedEmptyString()640 bool DecryptionStateMachine::IsEncryptedEmptyString() 641 { 642 return in_.IsEOF() && filter_count_ == 1 && buffer_.empty() && pending_buffer_.empty() 643 && format_ == Format::GPGOrNestedWad; 644 } 645 } 646 647