-
Notifications
You must be signed in to change notification settings - Fork 1.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Server certificate pinning for Store source #2347
Conversation
…ttached to source
…nual reconfiguration of builtin sources become fully builtin. In process of allowing test config via GP source definitions for E2E tests.
…s the just moved to a new chain.
src/CertificateResources/resource.h
Outdated
@@ -0,0 +1,14 @@ | |||
//{{NO_DEPENDENCIES}} | |||
// Microsoft Visual C++ generated include file. | |||
// Used by WinGetSchemas.rc |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@@ -2039,6 +2039,18 @@ class winhttp_client final : public _http_client_communicator | |||
case WINHTTP_CALLBACK_STATUS_SENDING_REQUEST: | |||
{ | |||
p_request_context->on_send_request_validate_cn(); | |||
|
|||
try |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The upstream codebase is in maintenance mode. We can try to get this change in there but it seems unlikely, and very unlikely to be able to do so in the time frame we need.
PolicyState localManifestFilesPolicy = GroupPolicies().GetState(TogglePolicy::Policy::LocalManifestFiles); | ||
if (localManifestFilesPolicy != PolicyState::NotConfigured) | ||
{ | ||
return (localManifestFilesPolicy == PolicyState::Enabled ? true : false); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@@ -196,6 +196,19 @@ namespace AppInstaller::Settings | |||
return std::nullopt; | |||
} | |||
|
|||
#ifndef AICLI_DISABLE_TEST_HOOKS | |||
// Enable certificate pinning configuration through GP sources for testing | |||
std::string pinningConfigurationName = "CertificatePinning"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@@ -196,6 +196,19 @@ namespace AppInstaller::Settings | |||
return std::nullopt; | |||
} | |||
|
|||
#ifndef AICLI_DISABLE_TEST_HOOKS |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Possibly, but I want that to be an explicit decision, not part of this change.
return *this; | ||
} | ||
|
||
bool PinningDetails::LoadFrom(const Json::Value& configuration) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
|
||
bool PinningDetails::LoadFrom(const Json::Value& configuration) | ||
{ | ||
std::string validationName = "Validation"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
&m_certificateContext.get()->pCertInfo->SubjectPublicKeyInfo, | ||
&certContext->pCertInfo->SubjectPublicKeyInfo)) | ||
{ | ||
AICLI_LOG(Core, Verbose, << "Public key mismatch: Expected certificate [" << GetSimpleDisplayName(m_certificateContext.get()) << "], Actual certificate [" << GetSimpleDisplayName(certContext) << "]"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I chose not to log them by default for the case of multiple chains. It is expected that only one of the chains would be valid for any given connection, so logging "Hey there is seemingly a security issue over here!" is not warranted when it turns out that it is actually just the one not being used.
PinningConfiguration::Validate
, which is the expected entry point for any non-test use case, does log at Error
on a failure to find any good chain. If that is happening, then the user can manually gather a verbose log.
if (m_chain.empty()) | ||
{ | ||
// An empty chain rejects all inputs. | ||
AICLI_LOG(Core, Verbose, << "Empty pinning chain blindly rejecting chain context"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I changed this line specifically to Warning
as an empty chain is not an expected use case.
|
||
std::wostringstream pinningConfig; | ||
pinningConfig << LR"({ | ||
"Chains": [{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: seems bad indentation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is a raw string, but I can bring the beginning down to make it look less awkward.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
/azp run |
Azure Pipelines successfully started running 1 pipeline(s). |
Change
This change adds a generic certificate chain verification infrastructure for pinning certificate chains. It is specifically used to pin the Microsoft Store source by default. More sources may be pinned later, but currently the packaged index is less in need of it because it is already signed.
The pinning configuration consists of 1 or more chains, only one of which needs to successfully validate the incoming certificate. This allows for rolling to a new certificate when needed. Each chain consists of a fixed set of certificates, which can each be configured to validate any or all of the following properties:
If the certificate is configured to validate none of the values, it will allow any certificate through.
An admin setting is added to disable pinning, both as an emergency measure in the event that there is a bug or rolled certificate that was not communicated, but also because there are test scenarios where the user actively wants to disable it (HTTPS redirection via something like Fiddler).
The configuration can be loaded from JSON for future dynamic configuration, but it is currently only as a test hook to enable configuration via Group Policy.
In order to better secure the source by default, reconfiguring (remove then add) the Store source manually will convert it back to the built-in values. This includes the pinning configuration.
It was necessary to modify the cpprestsdk subtree to add a new callback. This enables the request handle to be passed back to our code when the server certificate is first available. We can then check the server certificate against the configured pinning chain, making a decision to terminate the request before it is sent.
Validation
Unit tests are added for the various utilities and the primary pinning configuration/validation.
An E2E test is added for a negative case with the Store, but more could be done to test REST in general in our CI pipeline.
Microsoft Reviewers: Open in CodeFlow