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
Allow X509Chain to replace the root trust list for a single call #20302
Comments
This would be very handy for checking a certificate is pinned against a private root. |
@bartonjs estimates 3 days remaining |
How elaborate does the interface for this need to be? Looks like the implementation of this feature can be completed pretty easily once the interface this is hammered down ... |
@ShutterQuick Well, what does
If (2) (which is the better match for "Root"), does it throw for non-self-issued, or just ignore them? (macOS and OpenSSL are mostly designed as (1); Windows has an option, but defaults to (2)) A collection property should be non-null to allow easy addition; which means that clearing the collection means "revert to default trust" instead of "trust nothing", unless there's a secondary "I want to use customized trust" setting somewhere... but now there's an error state of not setting that option but putting things in the collection 😄. If we use the emptiness of the collection as customized trust, but we ignore non-self-issued certificates, So it's really a question of how to add in a model for this new notion where there are no surprises. |
Thanks for your answer, @bartonjs You raise some excellent points, and of course you're right, my suggestion of null to determine if the store should be active or not is obviously ridiculous. I agree that it might make more sense to allow adding trust anchors rather than roots, but as long as Windows 7 is supported that doesn't seem feasible. By controlling the behaviour of the new cert collection with a flag, it might be possible to enable that behaviour easily in the future when W7 support is dropped. Or already now on platforms that support it. E.g. something like this: public class X509ChainPolicy
{
...
X509Certificate2Collection CustomTrustStore { get; }
X509CustomTrustStoreMode CustomTrustStoreMode { get; set; }
...
}
...
[Flags]
public enum X509CustomTrustStoreMode : byte
{
Supplement = 0x00,
Replace = 0x01,
// AllowIntermediate = 0x80
} The supplement mode looks like it would be fairly trivial to implement on both Windows, OSX and OpenSSL, and it seems like it could be a fitting default mode, as Supplement with no certificates added to the collection is the same behaviour as today. Of course there's the question how useful a supplement mode would be. I'm struggling a little coming up with use cases where I'd want both my own anchors and whatever is in the system store. It might be that Lastly, I realize that due to my inexperience in these matters, my input might mostly be noise. |
I am going to start working on this feature. Let me know if anyone else has started it. |
@zwread my understanding of this issue is that new public APIs are needed in order to make this feature work (hence the This issue does not have the |
@zwread If you want to experiment and try to come up with an API surface that feels like it's hard to use incorrectly (which probably also means describing the exception model), that's great. But, as @vcsjones said, since there hasn't been an API proposal there hasn't been an API review, so jumping straight to a PR would be a bit of a faux pas. |
@bartonjs and @vcsjones, thank you for the information, that would have been unfortunate. I will definitely start off with coming up with an API surface then. My initial plan was similar to what @ShutterQuick mentioned in his last post. With a trust mode that would either allow the default root or a custom collection of certificates to replace it. The public class X509ChainPolicy
{
...
X509CertificateTrustStoreMode _certificateTrustMode;
X509Certificate2Collection _customTrustCollection;
...
public X509CertificateTrustStoreMode CertificateTrustMode
{
get
{
return _certificateTrustMode;
}
set
{
if (value < X509CertificateTrustMode.Root || value > X509CertificateTrustMode.Custom)
throw new ArgumentException(SR.Format(SR.Arg_EnumIllegalVal, nameof(value)));
_certitificateTrustMode = value;
}
}
...
public X509Certificate2Collection CustomTrustCollection => _customTrustCollection ?? (_customTrustCollection = new X509Certificate2Collection());
...
} public enum X509CertificateTrustMode
{
System = 0,
Custom = 1,
} In public class X509Chain
{
...
internal void CheckCertificateTrustMode()
{
if(_chainPolicy.CertificateTrustMode == X509CertificateTrustMode.Custom)
{
foreach(X509Certificate2 certificate in _chainPolicy.CustomTrustCollection)
{
if(certificate.IssuerName.RawData != certificate.SubjectName.RawData)
_chainPolicy.CustomTrustCollection.Remove(certificate);
}
if(_chainPolicy.CustomTrustCollection.Count() < 1)
throw new ArgumentException(SR.Cryptography_InvalidCertificateCollection, nameof(_chainPolicy.CustomTrustCollection));
}
}
...
} |
@zwread I like that it encompasses
Questions it leaves me with:
|
Thank you for the response, the simplest way I see of resolving these questions is to fix the validation in public class X509Chain
{
...
internal void CheckCertificateTrustMode()
{
if(_chainPolicy.CertificateTrustMode == X509CertificateTrustMode.Custom)
{
foreach(X509Certificate2 certificate in _chainPolicy.CustomTrustCollection)
{
if(certificate.IssuerName.RawData != certificate.SubjectName.RawData)
throw new ArgumentException(SR.Cryptography_InvalidCertificateCollection, nameof(_chainPolicy.CustomTrustCollection));
}
}
else if(_chainPolicy.CustomTrustCollection.Count() > 0)
throw new ArgumentException(SR.Cryptography_InvalidCertificateTrustMode, nameof(_chainPolicy.CustomTrustCollection));
}
...
} With this the collection is no longer being mutated and an exception is being thrown if the collection contains any non-self-issued certificates, or the mode is set to I might be misunderstanding, but could |
So, yeah, something like namespace System.Security.Cryptography.X509Certificates
{
public enum X509ChainTrustMode
{
System,
CustomRootTrust,
}
public partial class X509ChainPolicy
{
public X509ChainTrustMode TrustMode { get; set; }
public X509Certificate2Collection CustomTrustStore { get; }
}
} with a truth table of
Tests that I can think of without seeing code:
If you (@zwread) want to start work at this stage that's probably reasonable. If there's no objection to the name tweaks I just did, or the overall shape by the end of the day PDT (@vcsjones?) I'll mark it as api-ready-for-review. Since it isn't through the review process yet there's a chance that it'll change, but I'd expect only tweaking the names, if anything. If the Windows, macOS Security.framework and Unix OpenSSL implementations can all be done with tests and code review signed off by 17 May this can make it in for 3.0. (That would also include having all of the curly braces required by our style rules added back in from the most recent code snippet 😄) It gets exponentially less likely day-by-day after the 17th. Just setting some expectations. |
@bartonjs It's simple and I like it, I assume
That is a scenario I care about; I don't want to put my intermediates in CstuomTrustStore because I don't trust them, ultimately, I just want them to help build a path to a hardcoded root, while the extra store we periodically refresh from a remote service. |
Is that something worth enforcing? Does |
Yep.
The default for the custom trust chain builder on Windows is to ignore non-self-issued (I don't remember if they check the signature during the filter or not). They have a flag to opt in to doing "custom anchors" vs "custom roots". So, assuming we start with "custom roots", on macOS and Linux we'd need to copy the collection. Saying this, I guess we ultimately need to copy it anyways (into the respective native formats) so with sufficient tests we can make sure that The thing to double check when doing it is that none of the systems end up with "I filtered everything out" to mean "just use system trust". But that's achievable with tests. So... I guess I'm comfortable with removing that prefilter and just ensuring that there are tests for
|
OK, I think I like this API surface - it's simple enough. I'm less familiar with OpenSSL's APIs but this should be achievable on macOS using |
I've already scoped our shim down to "here's the STACK_OF(X509*) representing the trust list", so just requires using a different value thereabouts. |
One other thought - should the enum be Maybe Not for v1 of the implementation - but there may be a time where you want to specify additional roots to the other existing trust stores, not exclusive roots. OpenSSL (as you described it) and MacOS support this, though it seems like Win32 doesn't? Edit to clarify:
Hm. If Win32 can't do that, I guess not then - was trying to be forward thinking about the enum's values though. |
As far as I know, that's correct. While we could enumerate LocalMachine\Root, LocalMachine\ThirdPartyRoot, and CurrentUser\Root manually to add to the collection, it'd miss the WU backchannel for automatic trust.... and maybe other stuff that we don't accurately model in the Linux and macOS chains (because I don't know about it). The best answer would be "try explicit, if it failed, return system"... but a user who wanted could write that themselves. If it became a common thing we could pull it in to a new enum value ( |
For the second point here are we expecting the build to succeed? I'm just completing the Windows implementation and if we are replacing the system trust with |
I'd expect either a) the system trust stores are used as part of chain population (without trust assignment) or b) AIA-follow would kick in and it would find the certificate anyways. Either way, my expectation is that it would give UntrustedRoot, not PartialChain. If I'm wrong, that's OK, we'd just want to see what macOS does, and as long as that matches Windows we can make the Linux version match. |
Looks good as proposed. |
We're locking down 3.0 fairly soon. @zwread if you have this ready to go I think we might still be able to take it, but I'm pretty sure that past 3.0-preview7 the answer is that it's in the next version. (I marked the issue as Future because it's not something we "need" in 3.0, though it would be fantastic to have) |
We should add members on the ChainPolicy to allow a single call to X509Chain.Build to consider an X509Certificate2Collection of certs as a replacement of the system/user default certificate trust.
Win32: CERT_CHAIN_ENGINE_CONFIG looks like it has hExclusiveRoot for "replace".
Unix/OpenSSL: We just need to replace the root trust list when deciding whether or not to set the UnknownRoot flag.
Apple/Security.framework: The SecTrust APIs have options to replace the anchors list for a single request.
API requirements:
(edited to remove the option of "in addition to" the system trust due to increased complexity and less OS support)
The text was updated successfully, but these errors were encountered: