Certificates.cpp (25921B)
1 // Copyright (c) Microsoft Corporation. 2 // Licensed under the MIT License. 3 #include "pch.h" 4 #include "winget/Certificates.h" 5 #include "AppInstallerDateTime.h" 6 #include "AppInstallerLogging.h" 7 #include "AppInstallerStrings.h" 8 #include "winget/JsonUtil.h" 9 #include "winget/Resources.h" 10 11 namespace AppInstaller::Certificates 12 { 13 namespace 14 { 15 std::string GetNameString(PCCERT_CONTEXT certContext, DWORD nameType, bool forIssuer, void* typeParam = nullptr) 16 { 17 if (!certContext) 18 { 19 return "<no certificate loaded>"; 20 } 21 22 DWORD flags = forIssuer ? CERT_NAME_ISSUER_FLAG : 0; 23 24 DWORD characterCount = CertGetNameStringW(certContext, nameType, flags, typeParam, nullptr, 0); 25 std::wstring result(characterCount, L'\0'); 26 characterCount = CertGetNameStringW(certContext, nameType, flags, typeParam, &result[0], characterCount); 27 28 if (static_cast<size_t>(characterCount) == result.size()) 29 { 30 return Utility::ConvertToUTF8(static_cast<std::wstring_view>(result).substr(0, result.size() - 1)); 31 } 32 else 33 { 34 return "<unknown>"; 35 } 36 } 37 38 std::string GetSimpleDisplayName(PCCERT_CONTEXT certContext, bool forIssuer = false) 39 { 40 return GetNameString(certContext, CERT_NAME_SIMPLE_DISPLAY_TYPE, forIssuer); 41 } 42 43 std::string GetX500Name(PCCERT_CONTEXT certContext, bool forIssuer = false) 44 { 45 DWORD stringType = CERT_X500_NAME_STR; 46 return GetNameString(certContext, CERT_NAME_RDN_TYPE, forIssuer, &stringType); 47 } 48 49 std::string GetCommonName(PCCERT_CONTEXT certContext, bool forIssuer = false) 50 { 51 std::string commonName = szOID_COMMON_NAME; 52 return GetNameString(certContext, CERT_NAME_ATTR_TYPE, forIssuer, &commonName[0]); 53 } 54 55 std::string GetDescriptionOfCertChain(PCCERT_CHAIN_CONTEXT chainContext) 56 { 57 PCCERT_SIMPLE_CHAIN chain = chainContext->rgpChain[0]; 58 std::ostringstream stream; 59 std::string indent; 60 61 for (DWORD i = 0; i < chain->cElement; ++i) 62 { 63 PCCERT_CHAIN_ELEMENT element = chain->rgpElement[(chain->cElement - 1) - i]; 64 65 if (!indent.empty()) 66 { 67 stream << std::endl; 68 } 69 70 stream << indent; 71 72 stream << GetSimpleDisplayName(element->pCertContext); 73 74 indent.append(" "); 75 } 76 77 return std::move(stream).str(); 78 } 79 80 std::optional<PinningVerificationType> GetTypeFromString(std::string_view value) 81 { 82 std::string lowerValue = Utility::ToLower(value); 83 84 if (lowerValue == "none") 85 { 86 return PinningVerificationType::None; 87 } 88 else if (lowerValue == "publickey") 89 { 90 return PinningVerificationType::PublicKey; 91 } 92 else if (lowerValue == "subject") 93 { 94 return PinningVerificationType::Subject; 95 } 96 else if (lowerValue == "issuer") 97 { 98 return PinningVerificationType::Issuer; 99 } 100 else if (lowerValue == "anyissuer") 101 { 102 return PinningVerificationType::AnyIssuer; 103 } 104 else if (lowerValue == "requirenonleaf") 105 { 106 return PinningVerificationType::RequireNonLeaf; 107 } 108 109 return {}; 110 } 111 112 CertificateChainPosition GetCertificateChainPosition(DWORD index, DWORD count) 113 { 114 THROW_HR_IF(E_INVALIDARG, count == 0); 115 116 CertificateChainPosition position = CertificateChainPosition::Unknown; 117 118 if (index == 0) 119 { 120 position |= CertificateChainPosition::Root; 121 } 122 123 if (index > 0 && index < (count - 1)) 124 { 125 position |= CertificateChainPosition::Intermediate; 126 } 127 128 if (index == (count - 1)) 129 { 130 position |= CertificateChainPosition::Leaf; 131 } 132 133 return position; 134 } 135 } 136 137 std::ostream& operator<<(std::ostream& out, PinningVerificationType value) 138 { 139 if (value == PinningVerificationType::None) 140 { 141 out << "None"; 142 } 143 else 144 { 145 bool prepend = false; 146 147 for (const auto& flag : std::initializer_list<std::pair<PinningVerificationType, std::string_view>>{ 148 { PinningVerificationType::PublicKey, "PublicKey" }, 149 { PinningVerificationType::Subject, "Subject" }, 150 { PinningVerificationType::Issuer, "Issuer" }, 151 { PinningVerificationType::AnyIssuer, "AnyIssuer" }, 152 { PinningVerificationType::RequireNonLeaf, "RequireNonLeaf" }, 153 }) 154 { 155 if (WI_IsAnyFlagSet(value, flag.first)) 156 { 157 if (prepend) 158 { 159 out << " | "; 160 } 161 out << flag.second; 162 prepend = true; 163 } 164 } 165 } 166 167 return out; 168 } 169 170 std::ostream& operator<<(std::ostream& out, CertificateChainPosition value) 171 { 172 if (value == CertificateChainPosition::Unknown) 173 { 174 out << "Unknown"; 175 } 176 else 177 { 178 bool prepend = false; 179 180 for (const auto& flag : std::initializer_list<std::pair<CertificateChainPosition, std::string_view>>{ 181 { CertificateChainPosition::Root, "Root" }, 182 { CertificateChainPosition::Intermediate, "Intermediate" }, 183 { CertificateChainPosition::Leaf, "Leaf" }, 184 }) 185 { 186 if (WI_IsAnyFlagSet(value, flag.first)) 187 { 188 if (prepend) 189 { 190 out << " | "; 191 } 192 out << flag.second; 193 prepend = true; 194 } 195 } 196 } 197 198 return out; 199 } 200 201 PinningDetails& PinningDetails::LoadCertificate(int resource, int resourceType) 202 { 203 return LoadCertificate(Resource::GetResourceAsBytes(resource, resourceType)); 204 } 205 206 PinningDetails& PinningDetails::LoadCertificate(const std::vector<BYTE>& certificateBytes) 207 { 208 return LoadCertificate(std::make_pair(&certificateBytes[0], certificateBytes.size())); 209 } 210 211 PinningDetails& PinningDetails::LoadCertificate(const std::pair<const BYTE*, size_t> certificateBytes) 212 { 213 m_certificateContext.reset(CertCreateCertificateContext(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, certificateBytes.first, static_cast<DWORD>(certificateBytes.second))); 214 THROW_LAST_ERROR_IF(!m_certificateContext); 215 return *this; 216 } 217 218 PinningDetails& PinningDetails::SetPinning(PinningVerificationType type) 219 { 220 m_pinning = type; 221 return *this; 222 } 223 224 // The JSON is expected to look like: 225 // { 226 // "Validation":["publickey"], 227 // "EmbeddedCertificate":"<Hexadecimal string data for certificate>" 228 // } 229 bool PinningDetails::LoadFrom(const Json::Value& configuration) 230 { 231 const std::string validationName = "Validation"; 232 233 if (!configuration.isMember(validationName)) 234 { 235 AICLI_LOG(Core, Warning, << "Details JSON item has no member " << validationName); 236 return false; 237 } 238 239 auto validationValue = JSON::GetValue<std::vector<std::string>>(configuration[validationName]); 240 if (!validationValue) 241 { 242 AICLI_LOG(Core, Warning, << "Details JSON item member " << validationName << " was not an array of strings"); 243 return false; 244 } 245 246 for (const std::string& singleValidation : validationValue.value()) 247 { 248 auto validationType = GetTypeFromString(singleValidation); 249 250 if (!validationType) 251 { 252 AICLI_LOG(Core, Warning, << "Details JSON validation is unknown: " << singleValidation); 253 return false; 254 } 255 256 m_pinning |= validationType.value(); 257 } 258 259 if (m_pinning == PinningVerificationType::None) 260 { 261 // No need to load a certificate if not doing any pinning 262 return true; 263 } 264 265 const std::string embeddedCertificateName = "EmbeddedCertificate"; 266 267 if (!configuration.isMember(embeddedCertificateName)) 268 { 269 AICLI_LOG(Core, Warning, << "Details JSON item has no member " << embeddedCertificateName); 270 return false; 271 } 272 273 auto embeddedCertificateValue = JSON::GetValue<std::string>(configuration[embeddedCertificateName]); 274 if (!validationValue) 275 { 276 AICLI_LOG(Core, Warning, << "Details JSON item member " << embeddedCertificateName << " was not a string"); 277 return false; 278 } 279 280 auto embeddedCertificateBytes = Utility::ParseFromHexString(embeddedCertificateValue.value()); 281 LoadCertificate(embeddedCertificateBytes); 282 283 return true; 284 285 } 286 287 CertificatePinningValidationResult PinningDetails::Validate(PCCERT_CONTEXT certContext, CertificateChainPosition position) const 288 { 289 CertificatePinningValidationResult failResult = WI_IsFlagSet(m_pinning, PinningVerificationType::AnyIssuer) ? CertificatePinningValidationResult::Skipped : CertificatePinningValidationResult::Rejected; 290 291 if (WI_IsFlagSet(m_pinning, PinningVerificationType::RequireNonLeaf) && 292 WI_IsFlagSet(position, CertificateChainPosition::Leaf)) 293 { 294 AICLI_LOG(Core, Verbose, << "Required non-leaf mismatch: Expected certificate [" << GetSimpleDisplayName(m_certificateContext.get()) << "], Actual certificate [" << GetSimpleDisplayName(certContext) << "] was " << position); 295 return CertificatePinningValidationResult::Rejected; 296 } 297 298 if (WI_IsFlagSet(m_pinning, PinningVerificationType::PublicKey)) 299 { 300 THROW_HR_IF(E_NOT_VALID_STATE, !m_certificateContext); 301 302 if (!CertComparePublicKeyInfo( 303 X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, 304 &m_certificateContext.get()->pCertInfo->SubjectPublicKeyInfo, 305 &certContext->pCertInfo->SubjectPublicKeyInfo)) 306 { 307 AICLI_LOG(Core, Verbose, << "Public key mismatch: Expected certificate [" << GetSimpleDisplayName(m_certificateContext.get()) << "], Actual certificate [" << GetSimpleDisplayName(certContext) << "]"); 308 return failResult; 309 } 310 } 311 312 if (WI_IsFlagSet(m_pinning, PinningVerificationType::Subject)) 313 { 314 THROW_HR_IF(E_NOT_VALID_STATE, !m_certificateContext); 315 316 if (!CertCompareCertificateName( 317 X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, 318 &m_certificateContext.get()->pCertInfo->Subject, 319 &certContext->pCertInfo->Subject)) 320 { 321 AICLI_LOG(Core, Verbose, << "Subject mismatch: Expected certificate [" << GetSimpleDisplayName(m_certificateContext.get()) << "], Actual certificate [" << GetSimpleDisplayName(certContext) << "]"); 322 return failResult; 323 } 324 } 325 326 if (WI_IsFlagSet(m_pinning, PinningVerificationType::Issuer)) 327 { 328 THROW_HR_IF(E_NOT_VALID_STATE, !m_certificateContext); 329 330 if (!CertCompareCertificateName( 331 X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, 332 &m_certificateContext.get()->pCertInfo->Issuer, 333 &certContext->pCertInfo->Issuer)) 334 { 335 AICLI_LOG(Core, Verbose, << "Issuer mismatch: Expected certificate [" << GetSimpleDisplayName(m_certificateContext.get()) << "], Actual certificate [" << GetSimpleDisplayName(certContext) << "]"); 336 return failResult; 337 } 338 } 339 340 #ifndef AICLI_DISABLE_TEST_HOOKS 341 if (m_customValidation) 342 { 343 if (!m_customValidation(*this, certContext, position)) 344 { 345 AICLI_LOG(Core, Verbose, << "Custom validation returned false: Expected certificate [" << GetSimpleDisplayName(m_certificateContext.get()) << "], Actual certificate [" << GetSimpleDisplayName(certContext) << "]"); 346 return failResult; 347 } 348 } 349 #endif 350 351 return CertificatePinningValidationResult::Accepted; 352 } 353 354 void PinningDetails::OutputDescription(std::ostream& stream, std::string_view indent) const 355 { 356 stream << indent << GetSimpleDisplayName(m_certificateContext.get()) << " : " << m_pinning; 357 } 358 359 double PinningDetails::GetRemainingLifetimePercentage() const 360 { 361 THROW_HR_IF(E_NOT_VALID_STATE, !m_certificateContext); 362 363 auto notBefore = Utility::ConvertFiletimeToSystemClock(m_certificateContext.get()->pCertInfo->NotBefore); 364 auto notAfter = Utility::ConvertFiletimeToSystemClock(m_certificateContext.get()->pCertInfo->NotAfter); 365 THROW_HR_IF(E_NOT_VALID_STATE, notBefore > notAfter); 366 367 auto now = std::chrono::system_clock::now(); 368 369 if (now < notBefore) 370 { 371 return 1.0; 372 } 373 else if (now > notAfter) 374 { 375 return 0.0; 376 } 377 378 auto totalTime = notAfter - notBefore; 379 auto remainingTime = notAfter - now; 380 381 return static_cast<double>(remainingTime.count()) / static_cast<double>(totalTime.count()); 382 } 383 384 PinningChain::Node PinningChain::Node::Next() 385 { 386 if (!HasNext()) 387 { 388 m_chain.get().emplace_back(); 389 } 390 391 return { m_chain, m_index + 1 }; 392 } 393 394 const PinningChain::Node PinningChain::Node::Next() const 395 { 396 THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), !HasNext()); 397 return { m_chain, m_index + 1 }; 398 } 399 400 void PinningChain::Node::RemoveNext() 401 { 402 m_chain.get().erase(m_chain.get().begin() + m_index + 1, m_chain.get().end()); 403 } 404 405 bool PinningChain::Node::HasNext() const 406 { 407 return (m_index + 1 < m_chain.get().size()); 408 } 409 410 PinningChain::Node::Node(std::vector<PinningDetails>& chain, size_t index) : 411 m_chain(chain), m_index(index) {} 412 413 PinningChain::Node PinningChain::Root() 414 { 415 if (m_chain.empty()) 416 { 417 m_chain.emplace_back(); 418 } 419 420 return { m_chain, 0 }; 421 } 422 423 const PinningChain::Node PinningChain::Root() const 424 { 425 THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), m_chain.empty()); 426 return { const_cast<std::vector<PinningDetails>&>(m_chain), 0 }; 427 } 428 429 PinningChain& PinningChain::PartialChain(bool isPartial) 430 { 431 m_partial = isPartial; 432 return *this; 433 } 434 435 bool PinningChain::Validate(PCCERT_CHAIN_CONTEXT chainContext) const 436 { 437 if (m_chain.empty()) 438 { 439 // An empty chain rejects all inputs. 440 AICLI_LOG(Core, Warning, << "Empty pinning chain blindly rejecting chain context"); 441 return false; 442 } 443 444 THROW_HR_IF(E_INVALIDARG, chainContext->cChain == 0); 445 446 // Currently don't support chains bridged with CTLs; there must be only one simple chain that terminates in a trusted root. 447 if (chainContext->cChain > 1) 448 { 449 AICLI_LOG(Core, Verbose, << "Rejecting chain context with multiple chains"); 450 return false; 451 } 452 453 PCCERT_SIMPLE_CHAIN chain = chainContext->rgpChain[0]; 454 455 if (chain->TrustStatus.dwErrorStatus != CERT_TRUST_NO_ERROR) 456 { 457 AICLI_LOG(Core, Verbose, << "Rejecting simple chain context with bad TrustStatus: " << chain->TrustStatus.dwErrorStatus << " [" << chain->TrustStatus.dwInfoStatus << "]"); 458 return false; 459 } 460 461 if (chain->pTrustListInfo) 462 { 463 // This should not happen as the only reason for pTrustListInfo to be set is when `chainContext->cChain > 1`, which is rejected above 464 AICLI_LOG(Core, Verbose, << "Rejecting simple chain context with CTL info"); 465 return false; 466 } 467 468 if (!m_partial && static_cast<size_t>(chain->cElement) != m_chain.size()) 469 { 470 AICLI_LOG(Core, Verbose, << "Rejecting simple chain context based on size: expected " << m_chain.size() << ", got " << chain->cElement); 471 return false; 472 } 473 474 size_t currentDetailsIndex = 0; 475 476 for (DWORD i = 0; i < chain->cElement; ++i) 477 { 478 PCCERT_CHAIN_ELEMENT element = chain->rgpElement[(chain->cElement - 1) - i]; 479 480 if (element->TrustStatus.dwErrorStatus != CERT_TRUST_NO_ERROR) 481 { 482 AICLI_LOG(Core, Verbose, << "Rejecting chain element with bad TrustStatus: " << element->TrustStatus.dwErrorStatus << " [" << element->TrustStatus.dwInfoStatus << "]"); 483 return false; 484 } 485 486 CertificatePinningValidationResult result = m_chain[currentDetailsIndex].Validate(element->pCertContext, GetCertificateChainPosition(i, chain->cElement)); 487 488 if (result == CertificatePinningValidationResult::Rejected) 489 { 490 return false; 491 } 492 else if (result == CertificatePinningValidationResult::Accepted) 493 { 494 ++currentDetailsIndex; 495 } 496 else 497 { 498 THROW_HR_IF(E_UNEXPECTED, !m_partial || result != CertificatePinningValidationResult::Skipped); 499 AICLI_LOG(Core, Verbose, << "Skipping [" << GetSimpleDisplayName(element->pCertContext) << "] in partial chain validation."); 500 } 501 502 if (m_partial && m_chain.size() == currentDetailsIndex) 503 { 504 break; 505 } 506 } 507 508 // Ensure that all chain elements have been accepted 509 return m_chain.size() == currentDetailsIndex; 510 } 511 512 std::string PinningChain::GetDescription() const 513 { 514 if (m_chain.empty()) 515 { 516 return "<empty>"; 517 } 518 519 std::ostringstream stream; 520 std::string indent; 521 522 for (const PinningDetails& details : m_chain) 523 { 524 if (!indent.empty()) 525 { 526 stream << std::endl; 527 } 528 else if (m_partial) 529 { 530 stream << "[Partial Chain Validation]" << std::endl; 531 } 532 533 details.OutputDescription(stream, indent); 534 indent.append(" "); 535 } 536 537 return std::move(stream).str(); 538 } 539 540 // The JSON is expected to look like: 541 // { 542 // "Chain":[ 543 // { <See PinningDetails::LoadFrom> 544 // "Validation":["publickey"], 545 // "EmbeddedCertificate":"<Hexadecimal string data for certificate>" 546 // }, 547 // { 548 // "Validation":["subject","issuer"], 549 // "EmbeddedCertificate":"<Hexadecimal string data for certificate>" 550 // }, 551 // ... 552 // ] 553 // } 554 bool PinningChain::LoadFrom(const Json::Value& configuration) 555 { 556 const std::string chainName = "Chain"; 557 if (!configuration.isMember(chainName)) 558 { 559 AICLI_LOG(Core, Warning, << "Chains JSON item has no member " << chainName); 560 return false; 561 } 562 563 const auto& chain = configuration[chainName]; 564 if (!chain.isArray()) 565 { 566 AICLI_LOG(Core, Warning, << "Chain JSON input is not an array"); 567 return false; 568 } 569 570 for (const auto& configItem : chain) 571 { 572 PinningDetails details; 573 if (!details.LoadFrom(configItem)) 574 { 575 return false; 576 } 577 578 m_chain.emplace_back(std::move(details)); 579 } 580 581 return true; 582 } 583 584 double PinningChain::GetRemainingLifetimePercentage() const 585 { 586 double result = 1.0; 587 588 for (const auto& details : m_chain) 589 { 590 result = std::min(result, details.GetRemainingLifetimePercentage()); 591 } 592 593 return result; 594 } 595 596 PinningConfiguration::PinningConfiguration(std::string identifier) : m_identifier(identifier) 597 { 598 if (m_identifier.empty()) 599 { 600 GUID guid; 601 LOG_IF_FAILED(CoCreateGuid(&guid)); 602 wchar_t identifierBuffer[256] = {}; 603 (void)StringFromGUID2(guid, identifierBuffer, ARRAYSIZE(identifierBuffer)); 604 m_identifier = Utility::ConvertToUTF8(identifierBuffer); 605 } 606 } 607 608 void PinningConfiguration::AddChain(PinningChain chain) 609 { 610 AICLI_LOG(Core, Verbose, << "Adding chain to pinning configuration [" << m_identifier << "]:\n" << chain.GetDescription()); 611 m_configuration.emplace_back(std::move(chain)); 612 } 613 614 bool PinningConfiguration::Validate(PCCERT_CONTEXT certContext) const 615 { 616 if (m_configuration.empty()) 617 { 618 // No pinning configured 619 return true; 620 } 621 622 const BYTE* encodedBegin = certContext->pbCertEncoded; 623 const BYTE* encodedEnd = encodedBegin + certContext->cbCertEncoded; 624 if (certContext->cbCertEncoded == m_cachedCertificate.size() && 625 std::equal(encodedBegin, encodedEnd, m_cachedCertificate.begin())) 626 { 627 // We have seen this certificate and deemed it valid already. 628 return true; 629 } 630 631 // Get the chain for the given leaf certificate 632 wil::unique_cert_chain_context chainContext; 633 634 char oidPkixKpServerAuth[] = szOID_PKIX_KP_SERVER_AUTH; 635 std::array<char*, 1> chainUses = { 636 oidPkixKpServerAuth, 637 }; 638 639 CERT_CHAIN_PARA chainParameters = {}; 640 chainParameters.cbSize = sizeof(chainParameters); 641 chainParameters.RequestedUsage.dwType = USAGE_MATCH_TYPE_OR; 642 chainParameters.RequestedUsage.Usage.cUsageIdentifier = static_cast<DWORD>(chainUses.size()); 643 chainParameters.RequestedUsage.Usage.rgpszUsageIdentifier = chainUses.data(); 644 645 THROW_IF_WIN32_BOOL_FALSE(CertGetCertificateChain(nullptr, certContext, nullptr, certContext->hCertStore, &chainParameters, CERT_CHAIN_REVOCATION_CHECK_CHAIN, nullptr, &chainContext)); 646 647 bool result = false; 648 649 for (const auto& chain : m_configuration) 650 { 651 if (chain.Validate(chainContext.get())) 652 { 653 result = true; 654 break; 655 } 656 } 657 658 if (result) 659 { 660 // Only cache a successful validation 661 m_cachedCertificate.assign(encodedBegin, encodedEnd); 662 } 663 else 664 { 665 AICLI_LOG(Core, Error, << "Rejecting certificate [" << GetSimpleDisplayName(certContext) << "] as it did not match anything in pinning configuration [" << m_identifier << "]:\n" << GetDescriptionOfCertChain(chainContext.get())); 666 } 667 668 return result; 669 } 670 671 // The JSON is expected to look like: 672 // { 673 // "Chains":[ 674 // { <See PinningChain::LoadFrom> 675 // "Chain":[ 676 // { <See PinningDetails::LoadFrom> 677 // "Validation":["publickey"], 678 // "EmbeddedCertificate":"<Hexadecimal string data for certificate>" 679 // }, 680 // { 681 // "Validation":["subject","issuer"], 682 // "EmbeddedCertificate":"<Hexadecimal string data for certificate>" 683 // }, 684 // ... 685 // ] 686 // } 687 // ] 688 // } 689 bool PinningConfiguration::LoadFrom(const Json::Value& configuration) 690 { 691 const std::string chainsName = "Chains"; 692 if (!configuration.isMember(chainsName)) 693 { 694 AICLI_LOG(Core, Warning, << "PinningConfiguration JSON item has no member " << chainsName); 695 return false; 696 } 697 const auto& chains = configuration[chainsName]; 698 699 if (!chains.isArray()) 700 { 701 AICLI_LOG(Core, Warning, << "PinningConfiguration.Chains is not an array"); 702 return false; 703 } 704 705 std::vector<PinningChain> resultCache; 706 707 for (const auto& configItem : chains) 708 { 709 PinningChain chain; 710 if (!chain.LoadFrom(configItem)) 711 { 712 return false; 713 } 714 715 resultCache.emplace_back(std::move(chain)); 716 } 717 718 // Move all chains into the config now that we have succeeded 719 for (auto& result : resultCache) 720 { 721 AddChain(std::move(result)); 722 } 723 724 return true; 725 } 726 727 double PinningConfiguration::GetRemainingLifetimePercentage() const 728 { 729 double result = 0.0; 730 731 for (const auto& chain : m_configuration) 732 { 733 result = std::max(result, chain.GetRemainingLifetimePercentage()); 734 } 735 736 return result; 737 } 738 }