winget-cli

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

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 }