Skip to content

Commit 5d3bac1

Browse files
authored
New cert loader should load into CNG by default
When no provider attribute is present on a key, Windows loads the key into the CAPI Base provider unless PKCS12_PREFER_CNG_KSP is set. So, set that flag. On .NET Framework (or .NET Standard running on .NET Framework) we don't have the power to set that flag (without completely redefining how the PFX load loads), so inject a synthetic attribute to force keys into the CNG KSP when PreserveStorageProvider isn't set. Technically these two approaches differ when the incoming PFX has no name and PreserveStorageProvider is set (CoreFX: CNG, NetFX: CAPI Base), but that's unlikely, and consistent with .NET Framework imports.
1 parent c447338 commit 5d3bac1

File tree

7 files changed

+183
-26
lines changed

7 files changed

+183
-26
lines changed

src/libraries/Common/src/System/Security/Cryptography/X509Certificates/X509CertificateLoader.Pkcs12.cs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,15 @@ public static partial class X509CertificateLoader
2222
private const int NTE_FAIL = unchecked((int)0x80090020);
2323
#endif
2424

25+
#pragma warning disable CA1805
26+
private static readonly AttributeAsn? s_syntheticKspAttribute =
27+
#if NET
28+
null;
29+
#else
30+
BuildSyntheticKspAttribute();
31+
#endif
32+
#pragma warning restore CA1805
33+
2534
static partial void LoadPkcs12NoLimits(
2635
ReadOnlyMemory<byte> data,
2736
ReadOnlySpan<char> password,
@@ -366,6 +375,13 @@ private static void ProcessSafeContents(
366375
});
367376
}
368377

378+
if (!loaderLimits.PreserveStorageProvider && s_syntheticKspAttribute.HasValue)
379+
{
380+
int newCount = (bag.BagAttributes?.Length).GetValueOrDefault(0) + 1;
381+
Array.Resize(ref bag.BagAttributes, newCount);
382+
bag.BagAttributes[newCount - 1] = s_syntheticKspAttribute.GetValueOrDefault();
383+
}
384+
369385
bagState.AddKey(bag);
370386
}
371387
}
@@ -552,6 +568,20 @@ static int GetRawKdfCount(in AlgorithmIdentifierAsn algorithmIdentifier)
552568
}
553569
}
554570

571+
#if !NET
572+
private static AttributeAsn? BuildSyntheticKspAttribute()
573+
{
574+
AsnWriter writer = new AsnWriter(AsnEncodingRules.DER);
575+
writer.WriteCharacterString(UniversalTagNumber.BMPString, "Microsoft Software Key Storage Provider");
576+
577+
return new AttributeAsn
578+
{
579+
AttrType = Oids.MsPkcs12KeyProviderName,
580+
AttrValues = new[] { new ReadOnlyMemory<byte>(writer.Encode()) }
581+
};
582+
}
583+
#endif
584+
555585
private readonly partial struct Pkcs12Return
556586
{
557587
internal partial bool HasValue();

src/libraries/Common/tests/System/Security/Cryptography/X509Certificates/TestData.cs

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4630,5 +4630,58 @@ internal static DSAParameters GetDSA1024Params()
46304630
"007600690064006500721E4E004D006900630072006F0073006F006600740020" +
46314631
"0053006F0066007400770061007200650020004B00650079002000530074006F" +
46324632
"0072006100670065002000500072006F00760069006400650072").HexToByteArray();
4633+
4634+
// Uses Placeholder
4635+
internal static readonly byte[] SChannelPfx = (
4636+
"3082062A020103308205E606092A864886F70D010701A08205D7048205D33082" +
4637+
"05CF3082038806092A864886F70D010701A08203790482037530820371308203" +
4638+
"6D060B2A864886F70D010C0A0102A08202B6308202B2301C060A2A864886F70D" +
4639+
"010C0103300E040823A7915B5E0EECA9020207D0048202905D79973EDC12FE3C" +
4640+
"839C839008F3E79933BAA119C8015AF35C0DA932704D0C45990F2A60FFDFD26A" +
4641+
"4CC138A3A04673A79CE067AD6D1376608B1A0E61B5F9284A9D9D229ABB17EEFC" +
4642+
"870EB8CD61E6C054FA0AE0202B0FF04452BF3487B38FE256F406CFCA94547EC1" +
4643+
"BD44DA25A857E90990EAB58EAAD26322FDCD8810E4019B81670AD455FF905675" +
4644+
"DABDAC3331AA662AB08D1DCDE0B56CB0F3B8D53F5ABDB613772174B3959EBE75" +
4645+
"EF085404D60DC6161E576B641E5BFB60400C462BA5F9F69CDD2F4F48BA5A3C64" +
4646+
"509FCFF53EF2A7C5AA471762C9BCE99B2AD8C0E415795A816BED896B46C66FE8" +
4647+
"E737829614E0FCF2E3BDC68D24710DDC86FEA5329F8355EF1A51330981303DE8" +
4648+
"38F4CD00D3187CE52F03ACB9BD5A62F98FA1395DA14E3D5FA50F8B466488C0A0" +
4649+
"4780074FD330CA3539067A8A194CC63C2D0D35B3A61A6F8EC711907DB0D8E3D0" +
4650+
"6912F8202746264C5A9C0A9CBD7AACB1176519D901100C299126E0B30D869C14" +
4651+
"B68AA04E8EF3B144447976581BDD63E83DCDDBC8C5C185E2EA598CCAFC137BB0" +
4652+
"F46D053F900D46381915322BFF04F6EADE31F14D6781FD98764613BB679ECE45" +
4653+
"01821EBA6C0AF5E603151903B2A7F19BCBF623401E14C3456DA20128BD53DE70" +
4654+
"1422EFF06FB0DA901D68D55389BE369D76F65B5D4AA112B6636C12F4A7806BE1" +
4655+
"A8DAABCD827D65AEE41B9F2AB9E978E936309F54CC77A5E693161E84D4031DB4" +
4656+
"26440E61F01464CBE564C97BD0F6E23D4E803C9CBB65A79BAEECED26FD03FA41" +
4657+
"4EB2DEE34044DE434D18CE643C2AA1A12A7336522020498642BBC2F2B8132E84" +
4658+
"87227D130B37BA4376D57EF4CF57ADC56F9AB38F60235DAC7C7EC7ED1A537C25" +
4659+
"E3BDF6E5D1B647817CA8D730E4FC0A6013CB4FF8920F76CBD287A50D40818638" +
4660+
"D7E44168E65E26513181A3301306092A864886F70D0109153106040401000000" +
4661+
"302106092A864886F70D01091431141E12004E0061006D006500640020004B00" +
4662+
"650079306906092B0601040182371101315C1E5A004D006900630072006F0073" +
4663+
"006F00660074002000520053004100200053004300680061006E006E0065006C" +
4664+
"002000430072007900700074006F006700720061007000680069006300200050" +
4665+
"0072006F007600690064006500723082023F06092A864886F70D010706A08202" +
4666+
"303082022C0201003082022506092A864886F70D010701301C060A2A864886F7" +
4667+
"0D010C0103300E0408543FA90B580A66D0020207D0808201F8DE9CCF9441B460" +
4668+
"478D35A09462569A805A1067DA2BA99BB5E7AC02FCE133BD4DD5246861D614DD" +
4669+
"903F02D1D342CC5CB3A939D5DABAB1157007B30BB9C9099F29A62FA812EA5DE8" +
4670+
"460B3312D8490EE1DAA69E7E7946488B28E33CDBF5378DD288427ED276FAC685" +
4671+
"12E3CC1D4D5F6BEB297EF639A331970B5D33C723E7F66C64FEF8907DB8CDB3C4" +
4672+
"BE64B5DE333E0D900FC2F5AF6E782114F1A6704D58698C4EF9C796F4A054D985" +
4673+
"5570D3E9B268A321E7A1F655B77EEB45C198EB2A7B6D11A30E816ADB8CF5FCDE" +
4674+
"29ADDA29CA15E807862E607DB8B0E4A67A2DC4C167EBB0EE4ECAB3CB3F844CC5" +
4675+
"528B569128F27F6ECE1495E7ACFC34FC114A8906A872BAF40CDB8425431FD985" +
4676+
"DC0BAC262D7E8FB4F71DF5151E242C4C160252FC56DBAF16EF1C082A2E1C32BB" +
4677+
"992E3A12F21D86259A37FF6528D1FEB98DC64E79D0901F9873372D0C0659C917" +
4678+
"A2F1CFA7176EEE82292A58EEFAFAC104FA61AC124114C4067EB1211C9590714C" +
4679+
"A0F67B315E597AFD1BE546C61E58490C093842A926BB1CF3C4D27395C520A707" +
4680+
"294234316A3E1837E6521C2565845E244345790FD53DAAD00673596D66438CCD" +
4681+
"82E5EA088AC6D2928DACFD3BBC7B40FC55BBD502B6410B575AADE4D50F6DBDB7" +
4682+
"94A24BAAACF49C2CCF025D454A1B7541B159CBAC7F12D184B7ECA8F4A7B72805" +
4683+
"EE10774B5501BDEBDE4A61D0A3B22C9B0F303B301F300706052B0E03021A0414" +
4684+
"95BAC18221F16265AFDD6688C9FA59BE80616F830414C3365AE07D90C0CD656F" +
4685+
"15661D39D56F307AB983020207D0").HexToByteArray();
46334686
}
46344687
}

src/libraries/Common/tests/System/Security/Cryptography/X509Certificates/X509CertificateLoaderPkcs12CollectionTests.cs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -786,6 +786,31 @@ public void LoadWithDuplicateAttributes(bool allowDuplicates)
786786
}
787787
}
788788

789+
[Theory]
790+
[InlineData(false, false)]
791+
[InlineData(false, true)]
792+
[InlineData(true, false)]
793+
[InlineData(true, true)]
794+
public void LoadWithLegacyProvider(bool preserveStorageProvider, bool ephemeralIfPossible)
795+
{
796+
Pkcs12LoaderLimits limits = new Pkcs12LoaderLimits { PreserveStorageProvider = preserveStorageProvider };
797+
X509KeyStorageFlags flags = ephemeralIfPossible ? EphemeralIfPossible : X509KeyStorageFlags.DefaultKeySet;
798+
799+
// EphemeralKeySet is not available by name in the netfx build.
800+
const X509KeyStorageFlags EphemeralKeySet = (X509KeyStorageFlags)0x20;
801+
bool expectLegacy = (flags & EphemeralKeySet) == 0 && preserveStorageProvider;
802+
803+
X509Certificate2Collection coll = LoadPfxNoFile(TestData.SChannelPfx, TestData.PlaceholderPw, flags, limits);
804+
805+
using (new CollectionDisposer(coll))
806+
{
807+
foreach (X509Certificate2 cert in coll)
808+
{
809+
X509CertificateLoaderPkcs12Tests.VerifySChannelProvider(cert, expectLegacy);
810+
}
811+
}
812+
}
813+
789814
private sealed class CollectionDisposer : IDisposable
790815
{
791816
private readonly X509Certificate2Collection _coll;

src/libraries/Common/tests/System/Security/Cryptography/X509Certificates/X509CertificateLoaderPkcs12Tests.cs

Lines changed: 66 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,7 @@ public abstract partial class X509CertificateLoaderPkcs12Tests
179179
#if NETFRAMEWORK
180180
X509KeyStorageFlags.DefaultKeySet;
181181
#else
182-
PlatformDetection.UsesAppleCrypto ?
182+
PlatformDetection.UsesAppleCrypto ?
183183
X509KeyStorageFlags.DefaultKeySet :
184184
X509KeyStorageFlags.EphemeralKeySet;
185185
#endif
@@ -776,6 +776,71 @@ public void LoadWithDuplicateAttributes(bool allowDuplicates)
776776
}
777777
}
778778

779+
[Theory]
780+
[InlineData(false, false)]
781+
[InlineData(false, true)]
782+
[InlineData(true, false)]
783+
[InlineData(true, true)]
784+
public void LoadWithLegacyProvider(bool preserveStorageProvider, bool ephemeralIfPossible)
785+
{
786+
Pkcs12LoaderLimits limits = new Pkcs12LoaderLimits { PreserveStorageProvider = preserveStorageProvider, };
787+
X509KeyStorageFlags flags = ephemeralIfPossible ? EphemeralIfPossible : X509KeyStorageFlags.DefaultKeySet;
788+
789+
// EphemeralKeySet is not available by name in the netfx build.
790+
const X509KeyStorageFlags EphemeralKeySet = (X509KeyStorageFlags)0x20;
791+
bool expectLegacy = (flags & EphemeralKeySet) == 0 && preserveStorageProvider;
792+
793+
using (X509Certificate2 cert = LoadPfxNoFile(TestData.SChannelPfx, TestData.PlaceholderPw, flags, limits))
794+
{
795+
VerifySChannelProvider(cert, expectLegacy);
796+
}
797+
}
798+
799+
internal static void VerifySChannelProvider(X509Certificate2 cert, bool expectLegacy)
800+
{
801+
const string SChannelProviderName = "Microsoft RSA SChannel Cryptographic Provider";
802+
803+
Assert.True(cert.HasPrivateKey, "cert.HasPrivateKey");
804+
805+
using (RSA privateKey = cert.GetRSAPrivateKey())
806+
{
807+
Assert.NotNull(privateKey);
808+
809+
if (PlatformDetection.IsWindows)
810+
{
811+
string expectedProvider = expectLegacy ?
812+
SChannelProviderName :
813+
"Microsoft Software Key Storage Provider";
814+
815+
RSACng cng = Assert.IsType<RSACng>(privateKey);
816+
Assert.Equal(expectedProvider, cng.Key.Provider.Provider);
817+
}
818+
819+
if (PlatformDetection.IsNetFramework)
820+
{
821+
#pragma warning disable SYSLIB0028
822+
if (expectLegacy)
823+
{
824+
AsymmetricAlgorithm otherPrivateKeyInstance = cert.PrivateKey;
825+
826+
RSACryptoServiceProvider csp =
827+
Assert.IsType<RSACryptoServiceProvider>(otherPrivateKeyInstance);
828+
829+
Assert.Equal(SChannelProviderName, csp.CspKeyContainerInfo.ProviderName);
830+
}
831+
else
832+
{
833+
Assert.Throws<CryptographicException>(() => cert.PrivateKey);
834+
}
835+
#pragma warning restore SYSLIB0028
836+
}
837+
838+
// Regardless of the platform, the key should work (partially because of the CNG wrapper of CAPI keys)
839+
// Assert.NoThrow
840+
privateKey.SignData(TestData.SChannelPfx, HashAlgorithmName.SHA256, RSASignaturePadding.Pss);
841+
}
842+
}
843+
779844
#if NET
780845
[Theory]
781846
[InlineData(false)]

src/libraries/Microsoft.Bcl.Cryptography/tests/Microsoft.Bcl.Cryptography.Tests.csproj

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,13 +25,13 @@
2525
<Compile Include="$(CommonTestPath)System\Security\Cryptography\X509Certificates\TempFileHolder.cs"
2626
Link="CommonTest\System\Security\Cryptography\X509Certificates\TempFileHolder.cs" />
2727
<Compile Include="$(CommonTestPath)System\Security\Cryptography\X509Certificates\TestData.cs"
28-
Link="CommonTest\System\Security\Cryptography\509Certificates\TestData.cs" />
28+
Link="CommonTest\System\Security\Cryptography\X509Certificates\TestData.cs" />
2929
<Compile Include="$(CommonTestPath)System\Security\Cryptography\X509Certificates\X509CertificateLoaderPkcs12CollectionTests.cs"
30-
Link="CommonTest\System\Security\Cryptography\509Certificates\X509CertificateLoaderPkcs12CollectionTests.cs" />
30+
Link="CommonTest\System\Security\Cryptography\X509Certificates\X509CertificateLoaderPkcs12CollectionTests.cs" />
3131
<Compile Include="$(CommonTestPath)System\Security\Cryptography\X509Certificates\X509CertificateLoaderPkcs12Tests.cs"
32-
Link="CommonTest\System\Security\Cryptography\509Certificates\X509CertificateLoaderPkcs12Tests.cs" />
32+
Link="CommonTest\System\Security\Cryptography\X509Certificates\X509CertificateLoaderPkcs12Tests.cs" />
3333
<Compile Include="$(CommonTestPath)System\Security\Cryptography\X509Certificates\X509CertificateLoaderTests.cs"
34-
Link="CommonTest\System\Security\Cryptography\509Certificates\X509CertificateLoaderTests.cs" />
34+
Link="CommonTest\System\Security\Cryptography\X509Certificates\X509CertificateLoaderTests.cs" />
3535
<Compile Include="X509Certificates\TestFiles.cs" />
3636
</ItemGroup>
3737
<ItemGroup>

src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/StorePal.Windows.Import.cs

Lines changed: 0 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -212,26 +212,6 @@ internal static partial IStorePal FromSystemStore(string storeName, StoreLocatio
212212
return new StorePal(certStore);
213213
}
214214

215-
// this method maps a X509KeyStorageFlags enum to a combination of crypto API flags
216-
private static Interop.Crypt32.PfxCertStoreFlags MapKeyStorageFlags(X509KeyStorageFlags keyStorageFlags)
217-
{
218-
Interop.Crypt32.PfxCertStoreFlags dwFlags = 0;
219-
if ((keyStorageFlags & X509KeyStorageFlags.UserKeySet) == X509KeyStorageFlags.UserKeySet)
220-
dwFlags |= Interop.Crypt32.PfxCertStoreFlags.CRYPT_USER_KEYSET;
221-
else if ((keyStorageFlags & X509KeyStorageFlags.MachineKeySet) == X509KeyStorageFlags.MachineKeySet)
222-
dwFlags |= Interop.Crypt32.PfxCertStoreFlags.CRYPT_MACHINE_KEYSET;
223-
224-
if ((keyStorageFlags & X509KeyStorageFlags.Exportable) == X509KeyStorageFlags.Exportable)
225-
dwFlags |= Interop.Crypt32.PfxCertStoreFlags.CRYPT_EXPORTABLE;
226-
if ((keyStorageFlags & X509KeyStorageFlags.UserProtected) == X509KeyStorageFlags.UserProtected)
227-
dwFlags |= Interop.Crypt32.PfxCertStoreFlags.CRYPT_USER_PROTECTED;
228-
229-
if ((keyStorageFlags & X509KeyStorageFlags.EphemeralKeySet) == X509KeyStorageFlags.EphemeralKeySet)
230-
dwFlags |= Interop.Crypt32.PfxCertStoreFlags.PKCS12_NO_PERSIST_KEY | Interop.Crypt32.PfxCertStoreFlags.PKCS12_ALWAYS_CNG_KSP;
231-
232-
return dwFlags;
233-
}
234-
235215
// this method maps X509Store OpenFlags to a combination of crypto API flags
236216
private static Interop.Crypt32.CertStoreFlags MapX509StoreFlags(StoreLocation storeLocation, OpenFlags flags)
237217
{

src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/X509CertificateLoader.Windows.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -281,7 +281,8 @@ private static Interop.Crypt32.PfxCertStoreFlags MapKeyStorageFlags(X509KeyStora
281281
{
282282
Debug.Assert((keyStorageFlags & KeyStorageFlagsAll) == keyStorageFlags);
283283

284-
Interop.Crypt32.PfxCertStoreFlags pfxCertStoreFlags = 0;
284+
Interop.Crypt32.PfxCertStoreFlags pfxCertStoreFlags = Interop.Crypt32.PfxCertStoreFlags.PKCS12_PREFER_CNG_KSP;
285+
285286
if ((keyStorageFlags & X509KeyStorageFlags.UserKeySet) == X509KeyStorageFlags.UserKeySet)
286287
pfxCertStoreFlags |= Interop.Crypt32.PfxCertStoreFlags.CRYPT_USER_KEYSET;
287288
else if ((keyStorageFlags & X509KeyStorageFlags.MachineKeySet) == X509KeyStorageFlags.MachineKeySet)
@@ -297,7 +298,10 @@ private static Interop.Crypt32.PfxCertStoreFlags MapKeyStorageFlags(X509KeyStora
297298
// difficult to do SHA-2 RSA signatures with, simplifies the story for UWP, and reduces the
298299
// complexity of pointer interpretation.
299300
if ((keyStorageFlags & X509KeyStorageFlags.EphemeralKeySet) == X509KeyStorageFlags.EphemeralKeySet)
301+
{
302+
pfxCertStoreFlags &= ~Interop.Crypt32.PfxCertStoreFlags.PKCS12_PREFER_CNG_KSP;
300303
pfxCertStoreFlags |= Interop.Crypt32.PfxCertStoreFlags.PKCS12_NO_PERSIST_KEY | Interop.Crypt32.PfxCertStoreFlags.PKCS12_ALWAYS_CNG_KSP;
304+
}
301305

302306
// In .NET Framework loading a PFX then adding the key to the Windows Certificate Store would
303307
// enable a native application compiled against CAPI to find that private key and interoperate with it.

0 commit comments

Comments
 (0)