提交 c76ac565 编写于 作者: K Krzysztof Wicher 提交者: David Cantu

Merged PR 30724: [6.0] Apply iteration work limits to X509 certificate loading

Block password-less PKCS12 blobs on X509 certificate loadings/imports and prevent AIA fetching of non-cert types.
上级 320aeb73
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Diagnostics;
namespace System.Security.Cryptography
{
// Places KDF work limits on the current thread.
internal static class KdfWorkLimiter
{
[ThreadStatic]
private static State? t_state;
// Entry point: sets the iteration limit to a new value.
internal static void SetIterationLimit(ulong workLimit)
{
Debug.Assert(t_state == null, "This method is not intended to be called recursively.");
State state = new State();
state.RemainingAllowedWork = workLimit;
t_state = state;
}
internal static bool WasWorkLimitExceeded()
{
Debug.Assert(t_state != null, "This method should only be called within a protected block.");
return t_state.WorkLimitWasExceeded;
}
// Removes any iteration limit on the current thread.
internal static void ResetIterationLimit()
{
t_state = null;
}
// Records that we're about to perform some amount of work.
// Overflows if the work count is exceeded.
internal static void RecordIterations(int workCount)
{
RecordIterations((long)workCount);
}
// Records that we're about to perform some amount of work.
// Overflows if the work count is exceeded.
internal static void RecordIterations(long workCount)
{
State? state = t_state;
if (state == null)
{
return;
}
bool success = false;
if (workCount < 0)
{
throw new CryptographicException();
}
try
{
if (!state.WorkLimitWasExceeded)
{
state.RemainingAllowedWork = checked(state.RemainingAllowedWork - (ulong)workCount);
success = true;
}
}
finally
{
// If for any reason we failed, mark the thread as "no further work allowed" and
// normalize to CryptographicException.
if (!success)
{
state.RemainingAllowedWork = 0;
state.WorkLimitWasExceeded = true;
throw new CryptographicException();
}
}
}
private sealed class State
{
internal ulong RemainingAllowedWork;
internal bool WorkLimitWasExceeded;
}
}
}
...@@ -377,6 +377,7 @@ private static CryptographicException AlgorithmKdfRequiresChars(string algId) ...@@ -377,6 +377,7 @@ private static CryptographicException AlgorithmKdfRequiresChars(string algId)
Debug.Assert(pwdTmpBytes!.Length == 0); Debug.Assert(pwdTmpBytes!.Length == 0);
} }
KdfWorkLimiter.RecordIterations(iterationCount);
using (var pbkdf2 = new Rfc2898DeriveBytes(pwdTmpBytes, salt.ToArray(), iterationCount, prf)) using (var pbkdf2 = new Rfc2898DeriveBytes(pwdTmpBytes, salt.ToArray(), iterationCount, prf))
{ {
derivedKey = pbkdf2.GetBytes(keySizeBytes); derivedKey = pbkdf2.GetBytes(keySizeBytes);
......
...@@ -147,6 +147,7 @@ internal static class Pkcs12Kdf ...@@ -147,6 +147,7 @@ internal static class Pkcs12Kdf
I = IRented.AsSpan(0, ILen); I = IRented.AsSpan(0, ILen);
} }
KdfWorkLimiter.RecordIterations(iterationCount);
IncrementalHash hash = IncrementalHash.CreateHash(hashAlgorithm); IncrementalHash hash = IncrementalHash.CreateHash(hashAlgorithm);
try try
......
...@@ -157,7 +157,7 @@ public static X509Certificate2 CreateServerSelfSignedCertificate(string name = " ...@@ -157,7 +157,7 @@ public static X509Certificate2 CreateServerSelfSignedCertificate(string name = "
X509Certificate2 cert = req.CreateSelfSigned(start, end); X509Certificate2 cert = req.CreateSelfSigned(start, end);
if (PlatformDetection.IsWindows) if (PlatformDetection.IsWindows)
{ {
cert = new X509Certificate2(cert.Export(X509ContentType.Pfx)); cert = new X509Certificate2(cert.Export(X509ContentType.Pfx), (string?)null);
} }
return cert; return cert;
......
...@@ -7,6 +7,7 @@ ...@@ -7,6 +7,7 @@
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Web; using System.Web;
using Xunit;
namespace System.Security.Cryptography.X509Certificates.Tests.Common namespace System.Security.Cryptography.X509Certificates.Tests.Common
{ {
...@@ -29,6 +30,7 @@ internal sealed class RevocationResponder : IDisposable ...@@ -29,6 +30,7 @@ internal sealed class RevocationResponder : IDisposable
public string UriPrefix { get; } public string UriPrefix { get; }
public bool RespondEmpty { get; set; } public bool RespondEmpty { get; set; }
public AiaResponseKind AiaResponseKind { get; set; }
public TimeSpan ResponseDelay { get; set; } public TimeSpan ResponseDelay { get; set; }
public DelayedActionsFlag DelayedActions { get; set; } public DelayedActionsFlag DelayedActions { get; set; }
...@@ -181,13 +183,13 @@ private void HandleRequest(HttpListenerContext context, ref bool responded) ...@@ -181,13 +183,13 @@ private void HandleRequest(HttpListenerContext context, ref bool responded)
Thread.Sleep(ResponseDelay); Thread.Sleep(ResponseDelay);
} }
byte[] certData = RespondEmpty ? Array.Empty<byte>() : authority.GetCertData(); byte[] certData = RespondEmpty ? Array.Empty<byte>() : GetCertDataForAiaResponseKind(AiaResponseKind, authority);
responded = true; responded = true;
context.Response.StatusCode = 200; context.Response.StatusCode = 200;
context.Response.ContentType = "application/pkix-cert"; context.Response.ContentType = AiaResponseKindToContentType(AiaResponseKind);
context.Response.Close(certData, willBlock: true); context.Response.Close(certData, willBlock: true);
Trace($"Responded with {certData.Length}-byte certificate from {authority.SubjectName}."); Trace($"Responded with {certData.Length}-byte {AiaResponseKind} from {authority.SubjectName}.");
return; return;
} }
...@@ -295,6 +297,41 @@ private static HttpListener OpenListener(out string uriPrefix) ...@@ -295,6 +297,41 @@ private static HttpListener OpenListener(out string uriPrefix)
} }
} }
private static string AiaResponseKindToContentType(AiaResponseKind kind)
{
if (kind == AiaResponseKind.Cert)
{
return "application/pkix-cert";
}
else if (kind == AiaResponseKind.Pkcs12)
{
return "application/x-pkcs12";
}
else
{
Assert.True(false, $"Unknown value AiaResponseKind.`{kind}`.");
return null;
}
}
private static byte[] GetCertDataForAiaResponseKind(AiaResponseKind kind, CertificateAuthority authority)
{
if (kind == AiaResponseKind.Cert)
{
return authority.GetCertData();
}
else if (kind == AiaResponseKind.Pkcs12)
{
using X509Certificate2 cert = new X509Certificate2(authority.GetCertData());
return cert.Export(X509ContentType.Pkcs12);
}
else
{
Assert.True(false, $"Unknown value AiaResponseKind.`{kind}`.");
return null;
}
}
private static bool TryGetOcspRequestBytes(HttpListenerRequest request, string prefix, out byte[] requestBytes) private static bool TryGetOcspRequestBytes(HttpListenerRequest request, string prefix, out byte[] requestBytes)
{ {
requestBytes = null; requestBytes = null;
...@@ -425,4 +462,10 @@ public enum DelayedActionsFlag : byte ...@@ -425,4 +462,10 @@ public enum DelayedActionsFlag : byte
Aia = 0b100, Aia = 0b100,
All = 0b11111111 All = 0b11111111
} }
public enum AiaResponseKind
{
Cert = 0,
Pkcs12 = 1,
}
} }
...@@ -63,7 +63,7 @@ public static IEnumerable<object[]> SslStream_StreamToStream_Authentication_Succ ...@@ -63,7 +63,7 @@ public static IEnumerable<object[]> SslStream_StreamToStream_Authentication_Succ
using (X509Certificate2 clientCert = Configuration.Certificates.GetClientCertificate()) using (X509Certificate2 clientCert = Configuration.Certificates.GetClientCertificate())
{ {
yield return new object[] { new X509Certificate2(serverCert), new X509Certificate2(clientCert) }; yield return new object[] { new X509Certificate2(serverCert), new X509Certificate2(clientCert) };
yield return new object[] { new X509Certificate(serverCert.Export(X509ContentType.Pfx)), new X509Certificate(clientCert.Export(X509ContentType.Pfx)) }; yield return new object[] { new X509Certificate(serverCert.Export(X509ContentType.Pfx), (string)null, X509KeyStorageFlags.DefaultKeySet), new X509Certificate(clientCert.Export(X509ContentType.Pfx), (string)null, X509KeyStorageFlags.DefaultKeySet) };
} }
} }
......
...@@ -169,7 +169,7 @@ internal static (X509Certificate2 certificate, X509Certificate2Collection) Gener ...@@ -169,7 +169,7 @@ internal static (X509Certificate2 certificate, X509Certificate2Collection) Gener
if (PlatformDetection.IsWindows) if (PlatformDetection.IsWindows)
{ {
X509Certificate2 ephemeral = endEntity; X509Certificate2 ephemeral = endEntity;
endEntity = new X509Certificate2(endEntity.Export(X509ContentType.Pfx)); endEntity = new X509Certificate2(endEntity.Export(X509ContentType.Pfx), (string)null, X509KeyStorageFlags.DefaultKeySet);
ephemeral.Dispose(); ephemeral.Dispose();
} }
......
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks> <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<DefineConstants>$(DefineConstants);INTERNAL_ASYMMETRIC_IMPLEMENTATIONS</DefineConstants> <DefineConstants>$(DefineConstants);INTERNAL_ASYMMETRIC_IMPLEMENTATIONS</DefineConstants>
...@@ -132,6 +132,8 @@ ...@@ -132,6 +132,8 @@
Link="Common\System\Security\Cryptography\KeyFormatHelper.cs" /> Link="Common\System\Security\Cryptography\KeyFormatHelper.cs" />
<Compile Include="$(CommonPath)System\Security\Cryptography\KeyFormatHelper.Encrypted.cs" <Compile Include="$(CommonPath)System\Security\Cryptography\KeyFormatHelper.Encrypted.cs"
Link="Common\System\Security\Cryptography\KeyFormatHelper.Encrypted.cs" /> Link="Common\System\Security\Cryptography\KeyFormatHelper.Encrypted.cs" />
<Compile Include="$(CommonPath)System\Security\Cryptography\KdfWorkLimiter.cs"
Link="Common\System\Security\Cryptography\KdfWorkLimiter.cs" />
<Compile Include="$(CommonPath)System\Security\Cryptography\KeySizeHelpers.cs" <Compile Include="$(CommonPath)System\Security\Cryptography\KeySizeHelpers.cs"
Link="Common\System\Security\Cryptography\KeySizeHelpers.cs" /> Link="Common\System\Security\Cryptography\KeySizeHelpers.cs" />
<Compile Include="$(CommonPath)System\Security\Cryptography\Oids.cs" <Compile Include="$(CommonPath)System\Security\Cryptography\Oids.cs"
......
...@@ -212,6 +212,8 @@ ...@@ -212,6 +212,8 @@
Link="Common\System\Security\Cryptography\KeyFormatHelper.cs" /> Link="Common\System\Security\Cryptography\KeyFormatHelper.cs" />
<Compile Include="$(CommonPath)System\Security\Cryptography\KeyFormatHelper.Encrypted.cs" <Compile Include="$(CommonPath)System\Security\Cryptography\KeyFormatHelper.Encrypted.cs"
Link="Common\System\Security\Cryptography\KeyFormatHelper.Encrypted.cs" /> Link="Common\System\Security\Cryptography\KeyFormatHelper.Encrypted.cs" />
<Compile Include="$(CommonPath)System\Security\Cryptography\KdfWorkLimiter.cs"
Link="Common\System\Security\Cryptography\KdfWorkLimiter.cs" />
<Compile Include="$(CommonPath)System\Security\Cryptography\KeySizeHelpers.cs" <Compile Include="$(CommonPath)System\Security\Cryptography\KeySizeHelpers.cs"
Link="Common\System\Security\Cryptography\KeySizeHelpers.cs" /> Link="Common\System\Security\Cryptography\KeySizeHelpers.cs" />
<Compile Include="$(CommonPath)System\Security\Cryptography\Oids.cs" <Compile Include="$(CommonPath)System\Security\Cryptography\Oids.cs"
......
...@@ -7,8 +7,8 @@ ...@@ -7,8 +7,8 @@
<TargetFrameworks>$(NetCoreAppCurrent)-windows;$(NetCoreAppCurrent);netcoreapp3.1-windows;netcoreapp3.1;netstandard2.1-windows;netstandard2.1;netstandard2.0-windows;netstandard2.0;net461-windows</TargetFrameworks> <TargetFrameworks>$(NetCoreAppCurrent)-windows;$(NetCoreAppCurrent);netcoreapp3.1-windows;netcoreapp3.1;netstandard2.1-windows;netstandard2.1;netstandard2.0-windows;netstandard2.0;net461-windows</TargetFrameworks>
<IsPackable>true</IsPackable> <IsPackable>true</IsPackable>
<!-- If you enable GeneratePackageOnBuild for this package and bump ServicingVersion, make sure to also bump ServicingVersion in Microsoft.Windows.Compatibility.csproj once for the next release. --> <!-- If you enable GeneratePackageOnBuild for this package and bump ServicingVersion, make sure to also bump ServicingVersion in Microsoft.Windows.Compatibility.csproj once for the next release. -->
<GeneratePackageOnBuild>false</GeneratePackageOnBuild> <GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<ServicingVersion>2</ServicingVersion> <ServicingVersion>3</ServicingVersion>
<PackageDescription>Provides support for PKCS and CMS algorithms. <PackageDescription>Provides support for PKCS and CMS algorithms.
Commonly Used Types: Commonly Used Types:
...@@ -613,6 +613,8 @@ System.Security.Cryptography.Pkcs.EnvelopedCms</PackageDescription> ...@@ -613,6 +613,8 @@ System.Security.Cryptography.Pkcs.EnvelopedCms</PackageDescription>
<Link>Common\System\Security\Cryptography\Asn1\Pkcs7\EncryptedDataAsn.xml.cs</Link> <Link>Common\System\Security\Cryptography\Asn1\Pkcs7\EncryptedDataAsn.xml.cs</Link>
<DependentUpon>Common\System\Security\Cryptography\Asn1\Pkcs7\EncryptedDataAsn.xml</DependentUpon> <DependentUpon>Common\System\Security\Cryptography\Asn1\Pkcs7\EncryptedDataAsn.xml</DependentUpon>
</Compile> </Compile>
<Compile Include="$(CommonPath)System\Security\Cryptography\KdfWorkLimiter.cs"
Link="Common\System\Security\Cryptography\KdfWorkLimiter.cs" />
<Compile Include="$(CommonPath)System\Security\Cryptography\PasswordBasedEncryption.cs" <Compile Include="$(CommonPath)System\Security\Cryptography\PasswordBasedEncryption.cs"
Link="Common\System\Security\Cryptography\PasswordBasedEncryption.cs" /> Link="Common\System\Security\Cryptography\PasswordBasedEncryption.cs" />
<Compile Include="$(CommonPath)System\Security\Cryptography\Pkcs12Kdf.cs" <Compile Include="$(CommonPath)System\Security\Cryptography\Pkcs12Kdf.cs"
...@@ -650,7 +652,7 @@ System.Security.Cryptography.Pkcs.EnvelopedCms</PackageDescription> ...@@ -650,7 +652,7 @@ System.Security.Cryptography.Pkcs.EnvelopedCms</PackageDescription>
<ProjectReference Include="$(LibrariesProjectRoot)System.Formats.Asn1\src\System.Formats.Asn1.csproj" /> <ProjectReference Include="$(LibrariesProjectRoot)System.Formats.Asn1\src\System.Formats.Asn1.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup Condition="$(TargetFramework.StartsWith('netstandard2.0'))"> <ItemGroup Condition="$(TargetFramework.StartsWith('netstandard2.0'))">
<PackageReference Include="System.Buffers" Version="$(SystemBuffersVersion)" /> <PackageReference Include="System.Buffers" Version="$(SystemBuffersVersion)" />
<PackageReference Include="System.Memory" Version="$(SystemMemoryVersion)" /> <PackageReference Include="System.Memory" Version="$(SystemMemoryVersion)" />
<!-- S.R.C.Unsafe isn't a primary but transitive dependency and this P2P makes sure that the live version is used. --> <!-- S.R.C.Unsafe isn't a primary but transitive dependency and this P2P makes sure that the live version is used. -->
<ProjectReference Include="$(LibrariesProjectRoot)System.Runtime.CompilerServices.Unsafe\src\System.Runtime.CompilerServices.Unsafe.ilproj" PrivateAssets="all" /> <ProjectReference Include="$(LibrariesProjectRoot)System.Runtime.CompilerServices.Unsafe\src\System.Runtime.CompilerServices.Unsafe.ilproj" PrivateAssets="all" />
......
...@@ -48,7 +48,7 @@ public static ICertificatePal FromOtherCert(X509Certificate cert) ...@@ -48,7 +48,7 @@ public static ICertificatePal FromOtherCert(X509Certificate cert)
return new AndroidCertificatePal(handle); return new AndroidCertificatePal(handle);
} }
public static ICertificatePal FromBlob(ReadOnlySpan<byte> rawData, SafePasswordHandle password, X509KeyStorageFlags keyStorageFlags) private static ICertificatePal FromBlob(ReadOnlySpan<byte> rawData, SafePasswordHandle password, bool readingFromFile, X509KeyStorageFlags keyStorageFlags)
{ {
Debug.Assert(password != null); Debug.Assert(password != null);
...@@ -68,6 +68,8 @@ public static ICertificatePal FromBlob(ReadOnlySpan<byte> rawData, SafePasswordH ...@@ -68,6 +68,8 @@ public static ICertificatePal FromBlob(ReadOnlySpan<byte> rawData, SafePasswordH
throw new PlatformNotSupportedException(SR.Cryptography_X509_PKCS12_PersistKeySetNotSupported); throw new PlatformNotSupportedException(SR.Cryptography_X509_PKCS12_PersistKeySetNotSupported);
} }
X509Certificate.EnforceIterationCountLimit(rawData, readingFromFile, password.PasswordProvided);
return ReadPkcs12(rawData, password, ephemeralSpecified); return ReadPkcs12(rawData, password, ephemeralSpecified);
case X509ContentType.Cert: case X509ContentType.Cert:
default: default:
...@@ -86,10 +88,15 @@ public static ICertificatePal FromBlob(ReadOnlySpan<byte> rawData, SafePasswordH ...@@ -86,10 +88,15 @@ public static ICertificatePal FromBlob(ReadOnlySpan<byte> rawData, SafePasswordH
throw new CryptographicException(); throw new CryptographicException();
} }
public static ICertificatePal FromBlob(ReadOnlySpan<byte> rawData, SafePasswordHandle password, X509KeyStorageFlags keyStorageFlags)
{
return FromBlob(rawData, password, readingFromFile: false, keyStorageFlags);
}
public static ICertificatePal FromFile(string fileName, SafePasswordHandle password, X509KeyStorageFlags keyStorageFlags) public static ICertificatePal FromFile(string fileName, SafePasswordHandle password, X509KeyStorageFlags keyStorageFlags)
{ {
byte[] fileBytes = System.IO.File.ReadAllBytes(fileName); byte[] fileBytes = System.IO.File.ReadAllBytes(fileName);
return FromBlob(fileBytes, password, keyStorageFlags); return FromBlob(fileBytes, password, readingFromFile: true, keyStorageFlags);
} }
// Handles both DER and PEM // Handles both DER and PEM
......
...@@ -18,7 +18,7 @@ public static IStorePal FromHandle(IntPtr storeHandle) ...@@ -18,7 +18,7 @@ public static IStorePal FromHandle(IntPtr storeHandle)
throw new NotImplementedException($"{nameof(StorePal)}.{nameof(FromHandle)}"); throw new NotImplementedException($"{nameof(StorePal)}.{nameof(FromHandle)}");
} }
public static ILoaderPal FromBlob(ReadOnlySpan<byte> rawData, SafePasswordHandle password, X509KeyStorageFlags keyStorageFlags) private static ILoaderPal FromBlob(ReadOnlySpan<byte> rawData, SafePasswordHandle password, bool readingFromFile, X509KeyStorageFlags keyStorageFlags)
{ {
Debug.Assert(password != null); Debug.Assert(password != null);
...@@ -27,6 +27,7 @@ public static ILoaderPal FromBlob(ReadOnlySpan<byte> rawData, SafePasswordHandle ...@@ -27,6 +27,7 @@ public static ILoaderPal FromBlob(ReadOnlySpan<byte> rawData, SafePasswordHandle
if (contentType == X509ContentType.Pkcs12) if (contentType == X509ContentType.Pkcs12)
{ {
X509Certificate.EnforceIterationCountLimit(rawData, readingFromFile, password.PasswordProvided);
ICertificatePal[] certPals = ReadPkcs12Collection(rawData, password, ephemeralSpecified); ICertificatePal[] certPals = ReadPkcs12Collection(rawData, password, ephemeralSpecified);
return new AndroidCertLoader(certPals); return new AndroidCertLoader(certPals);
} }
...@@ -37,10 +38,15 @@ public static ILoaderPal FromBlob(ReadOnlySpan<byte> rawData, SafePasswordHandle ...@@ -37,10 +38,15 @@ public static ILoaderPal FromBlob(ReadOnlySpan<byte> rawData, SafePasswordHandle
} }
} }
public static ILoaderPal FromBlob(ReadOnlySpan<byte> rawData, SafePasswordHandle password, X509KeyStorageFlags keyStorageFlags)
{
return FromBlob(rawData, password, readingFromFile: false, keyStorageFlags: keyStorageFlags);
}
public static ILoaderPal FromFile(string fileName, SafePasswordHandle password, X509KeyStorageFlags keyStorageFlags) public static ILoaderPal FromFile(string fileName, SafePasswordHandle password, X509KeyStorageFlags keyStorageFlags)
{ {
byte[] fileBytes = File.ReadAllBytes(fileName); byte[] fileBytes = File.ReadAllBytes(fileName);
return FromBlob(fileBytes, password, keyStorageFlags); return FromBlob(fileBytes, password, readingFromFile: true, keyStorageFlags: keyStorageFlags);
} }
public static IExportPal FromCertificate(ICertificatePalCore cert) public static IExportPal FromCertificate(ICertificatePalCore cert)
......
...@@ -23,6 +23,15 @@ internal sealed partial class AppleCertificatePal : ICertificatePal ...@@ -23,6 +23,15 @@ internal sealed partial class AppleCertificatePal : ICertificatePal
ReadOnlySpan<byte> rawData, ReadOnlySpan<byte> rawData,
SafePasswordHandle password, SafePasswordHandle password,
X509KeyStorageFlags keyStorageFlags) X509KeyStorageFlags keyStorageFlags)
{
return FromBlob(rawData, password, readingFromFile: false, keyStorageFlags);
}
private static ICertificatePal FromBlob(
ReadOnlySpan<byte> rawData,
SafePasswordHandle password,
bool readingFromFile,
X509KeyStorageFlags keyStorageFlags)
{ {
Debug.Assert(password != null); Debug.Assert(password != null);
...@@ -47,6 +56,7 @@ internal sealed partial class AppleCertificatePal : ICertificatePal ...@@ -47,6 +56,7 @@ internal sealed partial class AppleCertificatePal : ICertificatePal
throw new PlatformNotSupportedException(SR.Cryptography_X509_NoEphemeralPfx); throw new PlatformNotSupportedException(SR.Cryptography_X509_NoEphemeralPfx);
} }
X509Certificate.EnforceIterationCountLimit(rawData, readingFromFile, password.PasswordProvided);
bool exportable = (keyStorageFlags & X509KeyStorageFlags.Exportable) == X509KeyStorageFlags.Exportable; bool exportable = (keyStorageFlags & X509KeyStorageFlags.Exportable) == X509KeyStorageFlags.Exportable;
bool persist = bool persist =
......
...@@ -76,7 +76,7 @@ public static ICertificatePal FromFile(string fileName, SafePasswordHandle passw ...@@ -76,7 +76,7 @@ public static ICertificatePal FromFile(string fileName, SafePasswordHandle passw
Debug.Assert(password != null); Debug.Assert(password != null);
byte[] fileBytes = System.IO.File.ReadAllBytes(fileName); byte[] fileBytes = System.IO.File.ReadAllBytes(fileName);
return FromBlob(fileBytes, password, keyStorageFlags); return FromBlob(fileBytes, password, readingFromFile: true, keyStorageFlags);
} }
internal AppleCertificatePal(SafeSecCertificateHandle certHandle) internal AppleCertificatePal(SafeSecCertificateHandle certHandle)
......
...@@ -26,6 +26,11 @@ public static IStorePal FromHandle(IntPtr storeHandle) ...@@ -26,6 +26,11 @@ public static IStorePal FromHandle(IntPtr storeHandle)
} }
public static ILoaderPal FromBlob(ReadOnlySpan<byte> rawData, SafePasswordHandle password, X509KeyStorageFlags keyStorageFlags) public static ILoaderPal FromBlob(ReadOnlySpan<byte> rawData, SafePasswordHandle password, X509KeyStorageFlags keyStorageFlags)
{
return FromBlob(rawData, password, readingFromFile: false, keyStorageFlags);
}
private static ILoaderPal FromBlob(ReadOnlySpan<byte> rawData, SafePasswordHandle password, bool readingFromFile, X509KeyStorageFlags keyStorageFlags)
{ {
Debug.Assert(password != null); Debug.Assert(password != null);
...@@ -38,6 +43,7 @@ public static ILoaderPal FromBlob(ReadOnlySpan<byte> rawData, SafePasswordHandle ...@@ -38,6 +43,7 @@ public static ILoaderPal FromBlob(ReadOnlySpan<byte> rawData, SafePasswordHandle
throw new PlatformNotSupportedException(SR.Cryptography_X509_NoEphemeralPfx); throw new PlatformNotSupportedException(SR.Cryptography_X509_NoEphemeralPfx);
} }
X509Certificate.EnforceIterationCountLimit(rawData, readingFromFile, password.PasswordProvided);
bool exportable = (keyStorageFlags & X509KeyStorageFlags.Exportable) == X509KeyStorageFlags.Exportable; bool exportable = (keyStorageFlags & X509KeyStorageFlags.Exportable) == X509KeyStorageFlags.Exportable;
bool persist = bool persist =
...@@ -87,7 +93,7 @@ public static ILoaderPal FromFile(string fileName, SafePasswordHandle password, ...@@ -87,7 +93,7 @@ public static ILoaderPal FromFile(string fileName, SafePasswordHandle password,
Debug.Assert(password != null); Debug.Assert(password != null);
byte[] fileBytes = File.ReadAllBytes(fileName); byte[] fileBytes = File.ReadAllBytes(fileName);
return FromBlob(fileBytes, password, keyStorageFlags); return FromBlob(fileBytes, password, readingFromFile: true, keyStorageFlags);
} }
public static IExportPal FromCertificate(ICertificatePalCore cert) public static IExportPal FromCertificate(ICertificatePalCore cert)
......
...@@ -28,6 +28,16 @@ internal static class CertificateAssetDownloader ...@@ -28,6 +28,16 @@ internal static class CertificateAssetDownloader
try try
{ {
X509ContentType contentType = X509Certificate2.GetCertContentType(data);
switch (contentType)
{
case X509ContentType.Cert:
case X509ContentType.Pkcs7:
break;
default:
return null;
}
X509Certificate2 certificate = new X509Certificate2(data); X509Certificate2 certificate = new X509Certificate2(data);
certificate.ThrowIfInvalid(); certificate.ThrowIfInvalid();
return certificate; return certificate;
......
...@@ -55,7 +55,7 @@ public static ICertificatePal FromBlob(ReadOnlySpan<byte> rawData, SafePasswordH ...@@ -55,7 +55,7 @@ public static ICertificatePal FromBlob(ReadOnlySpan<byte> rawData, SafePasswordH
TryReadX509Pem(rawData, out cert) || TryReadX509Pem(rawData, out cert) ||
PkcsFormatReader.TryReadPkcs7Der(rawData, out cert) || PkcsFormatReader.TryReadPkcs7Der(rawData, out cert) ||
PkcsFormatReader.TryReadPkcs7Pem(rawData, out cert) || PkcsFormatReader.TryReadPkcs7Pem(rawData, out cert) ||
PkcsFormatReader.TryReadPkcs12(rawData, password, ephemeralSpecified, out cert, out openSslException)) PkcsFormatReader.TryReadPkcs12(rawData, password, ephemeralSpecified, readingFromFile: false, out cert, out openSslException))
{ {
if (cert == null) if (cert == null)
{ {
...@@ -90,6 +90,7 @@ public static ICertificatePal FromFile(string fileName, SafePasswordHandle passw ...@@ -90,6 +90,7 @@ public static ICertificatePal FromFile(string fileName, SafePasswordHandle passw
File.ReadAllBytes(fileName), File.ReadAllBytes(fileName),
password, password,
ephemeralSpecified, ephemeralSpecified,
readingFromFile: true,
out pal, out pal,
out Exception? exception); out Exception? exception);
......
...@@ -6,6 +6,7 @@ ...@@ -6,6 +6,7 @@
using System.Diagnostics; using System.Diagnostics;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.Security.Cryptography; using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using Microsoft.Win32.SafeHandles; using Microsoft.Win32.SafeHandles;
namespace Internal.Cryptography.Pal namespace Internal.Cryptography.Pal
...@@ -257,6 +258,7 @@ internal static bool TryReadPkcs7Pem(SafeBioHandle bio, [NotNullWhen(true)] out ...@@ -257,6 +258,7 @@ internal static bool TryReadPkcs7Pem(SafeBioHandle bio, [NotNullWhen(true)] out
ReadOnlySpan<byte> rawData, ReadOnlySpan<byte> rawData,
SafePasswordHandle password, SafePasswordHandle password,
bool ephemeralSpecified, bool ephemeralSpecified,
bool readingFromFile,
[NotNullWhen(true)] out ICertificatePal? certPal, [NotNullWhen(true)] out ICertificatePal? certPal,
out Exception? openSslException) out Exception? openSslException)
{ {
...@@ -267,6 +269,7 @@ internal static bool TryReadPkcs7Pem(SafeBioHandle bio, [NotNullWhen(true)] out ...@@ -267,6 +269,7 @@ internal static bool TryReadPkcs7Pem(SafeBioHandle bio, [NotNullWhen(true)] out
password, password,
single: true, single: true,
ephemeralSpecified, ephemeralSpecified,
readingFromFile,
out certPal!, out certPal!,
out ignored, out ignored,
out openSslException); out openSslException);
...@@ -276,6 +279,7 @@ internal static bool TryReadPkcs7Pem(SafeBioHandle bio, [NotNullWhen(true)] out ...@@ -276,6 +279,7 @@ internal static bool TryReadPkcs7Pem(SafeBioHandle bio, [NotNullWhen(true)] out
ReadOnlySpan<byte> rawData, ReadOnlySpan<byte> rawData,
SafePasswordHandle password, SafePasswordHandle password,
bool ephemeralSpecified, bool ephemeralSpecified,
bool readingFromFile,
[NotNullWhen(true)] out List<ICertificatePal>? certPals, [NotNullWhen(true)] out List<ICertificatePal>? certPals,
out Exception? openSslException) out Exception? openSslException)
{ {
...@@ -286,6 +290,7 @@ internal static bool TryReadPkcs7Pem(SafeBioHandle bio, [NotNullWhen(true)] out ...@@ -286,6 +290,7 @@ internal static bool TryReadPkcs7Pem(SafeBioHandle bio, [NotNullWhen(true)] out
password, password,
single: false, single: false,
ephemeralSpecified, ephemeralSpecified,
readingFromFile,
out ignored, out ignored,
out certPals!, out certPals!,
out openSslException); out openSslException);
...@@ -296,6 +301,7 @@ internal static bool TryReadPkcs7Pem(SafeBioHandle bio, [NotNullWhen(true)] out ...@@ -296,6 +301,7 @@ internal static bool TryReadPkcs7Pem(SafeBioHandle bio, [NotNullWhen(true)] out
SafePasswordHandle password, SafePasswordHandle password,
bool single, bool single,
bool ephemeralSpecified, bool ephemeralSpecified,
bool readingFromFile,
out ICertificatePal? readPal, out ICertificatePal? readPal,
out List<ICertificatePal>? readCerts, out List<ICertificatePal>? readCerts,
out Exception? openSslException) out Exception? openSslException)
...@@ -312,18 +318,21 @@ internal static bool TryReadPkcs7Pem(SafeBioHandle bio, [NotNullWhen(true)] out ...@@ -312,18 +318,21 @@ internal static bool TryReadPkcs7Pem(SafeBioHandle bio, [NotNullWhen(true)] out
using (pfx) using (pfx)
{ {
return TryReadPkcs12(pfx, password, single, ephemeralSpecified, out readPal, out readCerts); return TryReadPkcs12(rawData, pfx, password, single, ephemeralSpecified, readingFromFile, out readPal, out readCerts);
} }
} }
private static bool TryReadPkcs12( private static bool TryReadPkcs12(
ReadOnlySpan<byte> rawData,
OpenSslPkcs12Reader pfx, OpenSslPkcs12Reader pfx,
SafePasswordHandle password, SafePasswordHandle password,
bool single, bool single,
bool ephemeralSpecified, bool ephemeralSpecified,
bool readingFromFile,
out ICertificatePal? readPal, out ICertificatePal? readPal,
out List<ICertificatePal>? readCerts) out List<ICertificatePal>? readCerts)
{ {
X509Certificate.EnforceIterationCountLimit(rawData, readingFromFile, password.PasswordProvided);
pfx.Decrypt(password, ephemeralSpecified); pfx.Decrypt(password, ephemeralSpecified);
if (single) if (single)
......
...@@ -40,7 +40,7 @@ public static ILoaderPal FromBlob(ReadOnlySpan<byte> rawData, SafePasswordHandle ...@@ -40,7 +40,7 @@ public static ILoaderPal FromBlob(ReadOnlySpan<byte> rawData, SafePasswordHandle
if (PkcsFormatReader.TryReadPkcs7Der(rawData, out certPals) || if (PkcsFormatReader.TryReadPkcs7Der(rawData, out certPals) ||
PkcsFormatReader.TryReadPkcs7Pem(rawData, out certPals) || PkcsFormatReader.TryReadPkcs7Pem(rawData, out certPals) ||
PkcsFormatReader.TryReadPkcs12(rawData, password, ephemeralSpecified, out certPals, out openSslException)) PkcsFormatReader.TryReadPkcs12(rawData, password, ephemeralSpecified, readingFromFile: false, out certPals, out openSslException))
{ {
Debug.Assert(certPals != null); Debug.Assert(certPals != null);
...@@ -111,7 +111,7 @@ public static ILoaderPal FromFile(string fileName, SafePasswordHandle password, ...@@ -111,7 +111,7 @@ public static ILoaderPal FromFile(string fileName, SafePasswordHandle password,
// Capture the exception so in case of failure, the call to BioSeek does not override it. // Capture the exception so in case of failure, the call to BioSeek does not override it.
Exception? openSslException; Exception? openSslException;
byte[] data = File.ReadAllBytes(fileName); byte[] data = File.ReadAllBytes(fileName);
if (PkcsFormatReader.TryReadPkcs12(data, password, ephemeralSpecified, out certPals, out openSslException)) if (PkcsFormatReader.TryReadPkcs12(data, password, ephemeralSpecified, readingFromFile: true, out certPals, out openSslException))
{ {
return ListToLoaderPal(certPals); return ListToLoaderPal(certPals);
} }
......
...@@ -83,7 +83,14 @@ out pCertContext ...@@ -83,7 +83,14 @@ out pCertContext
else if (contentType == ContentType.CERT_QUERY_CONTENT_PFX) else if (contentType == ContentType.CERT_QUERY_CONTENT_PFX)
{ {
if (loadFromFile) if (loadFromFile)
{
rawData = File.ReadAllBytes(fileName!); rawData = File.ReadAllBytes(fileName!);
}
else
{
X509Certificate.EnforceIterationCountLimit(rawData, readingFromFile: false, password.PasswordProvided);
}
pCertContext = FilterPFXStore(rawData, password, pfxCertStoreFlags); pCertContext = FilterPFXStore(rawData, password, pfxCertStoreFlags);
// If PersistKeySet is set we don't delete the key, so that it persists. // If PersistKeySet is set we don't delete the key, so that it persists.
......
...@@ -6,6 +6,7 @@ ...@@ -6,6 +6,7 @@
using System; using System;
using System.IO; using System.IO;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates; using System.Security.Cryptography.X509Certificates;
using System.Diagnostics; using System.Diagnostics;
...@@ -68,6 +69,11 @@ private static StorePal FromBlobOrFile(ReadOnlySpan<byte> rawData, string? fileN ...@@ -68,6 +69,11 @@ private static StorePal FromBlobOrFile(ReadOnlySpan<byte> rawData, string? fileN
{ {
rawData = File.ReadAllBytes(fileName!); rawData = File.ReadAllBytes(fileName!);
} }
else
{
X509Certificate.EnforceIterationCountLimit(rawData, readingFromFile: false, password.PasswordProvided);
}
fixed (byte* pRawData2 = rawData) fixed (byte* pRawData2 = rawData)
{ {
CRYPTOAPI_BLOB blob2 = new CRYPTOAPI_BLOB(rawData!.Length, pRawData2); CRYPTOAPI_BLOB blob2 = new CRYPTOAPI_BLOB(rawData!.Length, pRawData2);
......
...@@ -99,6 +99,7 @@ internal static X509ContentType GetDerCertContentType(ReadOnlySpan<byte> rawData ...@@ -99,6 +99,7 @@ internal static X509ContentType GetDerCertContentType(ReadOnlySpan<byte> rawData
ReadOnlySpan<byte> rawData, ReadOnlySpan<byte> rawData,
X509ContentType contentType, X509ContentType contentType,
SafePasswordHandle password, SafePasswordHandle password,
bool readingFromFile,
X509KeyStorageFlags keyStorageFlags) X509KeyStorageFlags keyStorageFlags)
{ {
Debug.Assert(password != null); Debug.Assert(password != null);
...@@ -124,6 +125,7 @@ internal static X509ContentType GetDerCertContentType(ReadOnlySpan<byte> rawData ...@@ -124,6 +125,7 @@ internal static X509ContentType GetDerCertContentType(ReadOnlySpan<byte> rawData
throw new PlatformNotSupportedException(SR.Cryptography_X509_PKCS12_PersistKeySetNotSupported); throw new PlatformNotSupportedException(SR.Cryptography_X509_PKCS12_PersistKeySetNotSupported);
} }
X509Certificate.EnforceIterationCountLimit(rawData, readingFromFile, password.PasswordProvided);
return ImportPkcs12(rawData, password, ephemeralSpecified); return ImportPkcs12(rawData, password, ephemeralSpecified);
} }
...@@ -151,6 +153,15 @@ internal static X509ContentType GetDerCertContentType(ReadOnlySpan<byte> rawData ...@@ -151,6 +153,15 @@ internal static X509ContentType GetDerCertContentType(ReadOnlySpan<byte> rawData
ReadOnlySpan<byte> rawData, ReadOnlySpan<byte> rawData,
SafePasswordHandle password, SafePasswordHandle password,
X509KeyStorageFlags keyStorageFlags) X509KeyStorageFlags keyStorageFlags)
{
return FromBlob(rawData, password, readingFromFile: false, keyStorageFlags);
}
public static ICertificatePal FromBlob(
ReadOnlySpan<byte> rawData,
SafePasswordHandle password,
bool readingFromFile,
X509KeyStorageFlags keyStorageFlags)
{ {
Debug.Assert(password != null); Debug.Assert(password != null);
...@@ -159,11 +170,11 @@ internal static X509ContentType GetDerCertContentType(ReadOnlySpan<byte> rawData ...@@ -159,11 +170,11 @@ internal static X509ContentType GetDerCertContentType(ReadOnlySpan<byte> rawData
rawData, rawData,
(derData, contentType) => (derData, contentType) =>
{ {
result = FromDerBlob(derData, contentType, password, keyStorageFlags); result = FromDerBlob(derData, contentType, password, readingFromFile, keyStorageFlags);
return false; return false;
}); });
return result ?? FromDerBlob(rawData, GetDerCertContentType(rawData), password, keyStorageFlags); return result ?? FromDerBlob(rawData, GetDerCertContentType(rawData), password, readingFromFile, keyStorageFlags);
} }
public void DisposeTempKeychain() public void DisposeTempKeychain()
......
...@@ -18,7 +18,7 @@ namespace Internal.Cryptography.Pal ...@@ -18,7 +18,7 @@ namespace Internal.Cryptography.Pal
{ {
internal sealed partial class AppleCertificatePal : ICertificatePal internal sealed partial class AppleCertificatePal : ICertificatePal
{ {
private static SafePasswordHandle s_passwordExportHandle = new SafePasswordHandle("DotnetExportPassphrase"); private static SafePasswordHandle s_passwordExportHandle = new SafePasswordHandle("DotnetExportPassphrase", passwordProvided: true);
private static AppleCertificatePal ImportPkcs12( private static AppleCertificatePal ImportPkcs12(
ReadOnlySpan<byte> rawData, ReadOnlySpan<byte> rawData,
......
...@@ -19,7 +19,7 @@ public static IStorePal FromHandle(IntPtr storeHandle) ...@@ -19,7 +19,7 @@ public static IStorePal FromHandle(IntPtr storeHandle)
throw new PlatformNotSupportedException($"{nameof(StorePal)}.{nameof(FromHandle)}"); throw new PlatformNotSupportedException($"{nameof(StorePal)}.{nameof(FromHandle)}");
} }
public static ILoaderPal FromBlob(ReadOnlySpan<byte> rawData, SafePasswordHandle password, X509KeyStorageFlags keyStorageFlags) private static ILoaderPal FromBlob(ReadOnlySpan<byte> rawData, SafePasswordHandle password, bool readingFromFile, X509KeyStorageFlags keyStorageFlags)
{ {
List<ICertificatePal>? certificateList = null; List<ICertificatePal>? certificateList = null;
...@@ -28,7 +28,7 @@ public static ILoaderPal FromBlob(ReadOnlySpan<byte> rawData, SafePasswordHandle ...@@ -28,7 +28,7 @@ public static ILoaderPal FromBlob(ReadOnlySpan<byte> rawData, SafePasswordHandle
(derData, contentType) => (derData, contentType) =>
{ {
certificateList = certificateList ?? new List<ICertificatePal>(); certificateList = certificateList ?? new List<ICertificatePal>();
certificateList.Add(AppleCertificatePal.FromDerBlob(derData, contentType, password, keyStorageFlags)); certificateList.Add(AppleCertificatePal.FromDerBlob(derData, contentType, password, readingFromFile, keyStorageFlags));
return true; return true;
}); });
...@@ -49,6 +49,7 @@ public static ILoaderPal FromBlob(ReadOnlySpan<byte> rawData, SafePasswordHandle ...@@ -49,6 +49,7 @@ public static ILoaderPal FromBlob(ReadOnlySpan<byte> rawData, SafePasswordHandle
if (contentType == X509ContentType.Pkcs12) if (contentType == X509ContentType.Pkcs12)
{ {
X509Certificate.EnforceIterationCountLimit(rawData, readingFromFile, password.PasswordProvided);
ApplePkcs12Reader reader = new ApplePkcs12Reader(rawData); ApplePkcs12Reader reader = new ApplePkcs12Reader(rawData);
try try
...@@ -98,12 +99,17 @@ public static ILoaderPal FromBlob(ReadOnlySpan<byte> rawData, SafePasswordHandle ...@@ -98,12 +99,17 @@ public static ILoaderPal FromBlob(ReadOnlySpan<byte> rawData, SafePasswordHandle
return new CertCollectionLoader(certificateList); return new CertCollectionLoader(certificateList);
} }
public static ILoaderPal FromBlob(ReadOnlySpan<byte> rawData, SafePasswordHandle password, X509KeyStorageFlags keyStorageFlags)
{
return FromBlob(rawData, password, readingFromFile: false, keyStorageFlags);
}
public static ILoaderPal FromFile(string fileName, SafePasswordHandle password, X509KeyStorageFlags keyStorageFlags) public static ILoaderPal FromFile(string fileName, SafePasswordHandle password, X509KeyStorageFlags keyStorageFlags)
{ {
Debug.Assert(password != null); Debug.Assert(password != null);
byte[] fileBytes = File.ReadAllBytes(fileName); byte[] fileBytes = File.ReadAllBytes(fileName);
return FromBlob(fileBytes, password, keyStorageFlags); return FromBlob(fileBytes, password, readingFromFile: true, keyStorageFlags);
} }
public static IExportPal FromCertificate(ICertificatePalCore cert) public static IExportPal FromCertificate(ICertificatePalCore cert)
......
...@@ -16,7 +16,13 @@ internal sealed partial class SafePasswordHandle : SafeHandleZeroOrMinusOneIsInv ...@@ -16,7 +16,13 @@ internal sealed partial class SafePasswordHandle : SafeHandleZeroOrMinusOneIsInv
{ {
internal int Length { get; private set; } internal int Length { get; private set; }
public SafePasswordHandle(string? password) /// <summary>
/// This is used to track if a password was explicitly provided.
/// A null/empty password is a valid password.
/// </summary>
internal bool PasswordProvided { get; }
public SafePasswordHandle(string? password, bool passwordProvided)
: base(ownsHandle: true) : base(ownsHandle: true)
{ {
if (password != null) if (password != null)
...@@ -24,9 +30,11 @@ public SafePasswordHandle(string? password) ...@@ -24,9 +30,11 @@ public SafePasswordHandle(string? password)
handle = Marshal.StringToHGlobalUni(password); handle = Marshal.StringToHGlobalUni(password);
Length = password.Length; Length = password.Length;
} }
PasswordProvided = passwordProvided;
} }
public SafePasswordHandle(ReadOnlySpan<char> password) public SafePasswordHandle(ReadOnlySpan<char> password, bool passwordProvided)
: base(ownsHandle: true) : base(ownsHandle: true)
{ {
// "".AsSpan() is not default, so this is compat for "null tries NULL first". // "".AsSpan() is not default, so this is compat for "null tries NULL first".
...@@ -49,9 +57,11 @@ public SafePasswordHandle(ReadOnlySpan<char> password) ...@@ -49,9 +57,11 @@ public SafePasswordHandle(ReadOnlySpan<char> password)
Length = password.Length; Length = password.Length;
} }
PasswordProvided = passwordProvided;
} }
public SafePasswordHandle(SecureString? password) public SafePasswordHandle(SecureString? password, bool passwordProvided)
: base(ownsHandle: true) : base(ownsHandle: true)
{ {
if (password != null) if (password != null)
...@@ -59,6 +69,8 @@ public SafePasswordHandle(SecureString? password) ...@@ -59,6 +69,8 @@ public SafePasswordHandle(SecureString? password)
handle = Marshal.SecureStringToGlobalAllocUnicode(password); handle = Marshal.SecureStringToGlobalAllocUnicode(password);
Length = password.Length; Length = password.Length;
} }
PasswordProvided = passwordProvided;
} }
protected override bool ReleaseHandle() protected override bool ReleaseHandle()
...@@ -96,7 +108,7 @@ internal ReadOnlySpan<char> DangerousGetSpan() ...@@ -96,7 +108,7 @@ internal ReadOnlySpan<char> DangerousGetSpan()
SafeHandleCache<SafePasswordHandle>.GetInvalidHandle( SafeHandleCache<SafePasswordHandle>.GetInvalidHandle(
() => () =>
{ {
var handle = new SafePasswordHandle((string?)null); var handle = new SafePasswordHandle((string?)null, false);
handle.handle = (IntPtr)(-1); handle.handle = (IntPtr)(-1);
return handle; return handle;
}); });
......
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<root> <root>
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata"> <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" /> <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
...@@ -490,4 +490,7 @@ ...@@ -490,4 +490,7 @@
<data name="Cryptography_UnknownCertContentType" xml:space="preserve"> <data name="Cryptography_UnknownCertContentType" xml:space="preserve">
<value>The certificate content type could not be determined.</value> <value>The certificate content type could not be determined.</value>
</data> </data>
<data name="Cryptography_X509_PfxWithoutPassword" xml:space="preserve">
<value>PKCS12 (PFX) without a supplied password has exceeded maximum allowed iterations. See https://go.microsoft.com/fwlink/?linkid=2233907 for more information.</value>
</data>
</root> </root>
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using Internal.Cryptography;
using System.Diagnostics;
using System.Formats.Asn1;
using System.Security.Cryptography.Asn1.Pkcs7;
using System.Security.Cryptography.Asn1.Pkcs12;
namespace System.Security.Cryptography.Asn1.Pkcs12
{
internal partial struct PfxAsn
{
private const int MaxIterationWork = 300_000;
private static ReadOnlySpan<char> EmptyPassword => ""; // don't use ReadOnlySpan<byte>.Empty because it will get confused with default.
private static ReadOnlySpan<char> NullPassword => default;
internal ulong CountTotalIterations()
{
checked
{
ulong count = 0;
// RFC 7292 section 4.1:
// the contentType field of authSafe shall be of type data
// or signedData. The content field of the authSafe shall, either
// directly (data case) or indirectly (signedData case), contain a BER-
// encoded value of type AuthenticatedSafe.
// We don't support authSafe that is signedData, so enforce that it's just data.
if (AuthSafe.ContentType != Oids.Pkcs7Data)
{
throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding);
}
ReadOnlyMemory<byte> authSafeContents = Helpers.DecodeOctetStringAsMemory(AuthSafe.Content);
AsnValueReader outerAuthSafe = new AsnValueReader(authSafeContents.Span, AsnEncodingRules.BER); // RFC 7292 PDU says BER
AsnValueReader authSafeReader = outerAuthSafe.ReadSequence();
outerAuthSafe.ThrowIfNotEmpty();
bool hasSeenEncryptedInfo = false;
while (authSafeReader.HasData)
{
ContentInfoAsn.Decode(ref authSafeReader, authSafeContents, out ContentInfoAsn contentInfo);
ReadOnlyMemory<byte> contentData;
ArraySegment<byte>? rentedData = null;
try
{
if (contentInfo.ContentType != Oids.Pkcs7Data)
{
if (contentInfo.ContentType == Oids.Pkcs7Encrypted)
{
if (hasSeenEncryptedInfo)
{
// We will process at most one encryptedData ContentInfo. This is the most typical scenario where
// certificates are stored in an encryptedData ContentInfo, and keys are shrouded in a data ContentInfo.
throw new CryptographicException(SR.Cryptography_X509_PfxWithoutPassword);
}
ArraySegment<byte> content = DecryptContentInfo(contentInfo, out uint iterations);
contentData = content;
rentedData = content;
hasSeenEncryptedInfo = true;
count += iterations;
}
else
{
// Not a common scenario. It's not data or encryptedData, so they need to go through the
// regular PKCS12 loader.
throw new CryptographicException(SR.Cryptography_X509_PfxWithoutPassword);
}
}
else
{
contentData = Helpers.DecodeOctetStringAsMemory(contentInfo.Content);
}
AsnValueReader outerSafeBag = new AsnValueReader(contentData.Span, AsnEncodingRules.BER);
AsnValueReader safeBagReader = outerSafeBag.ReadSequence();
outerSafeBag.ThrowIfNotEmpty();
while (safeBagReader.HasData)
{
SafeBagAsn.Decode(ref safeBagReader, contentData, out SafeBagAsn bag);
// We only need to count iterations on PKCS8ShroudedKeyBag.
// * KeyBag is PKCS#8 PrivateKeyInfo and doesn't do iterations.
// * CertBag, either for x509Certificate or sdsiCertificate don't do iterations.
// * CRLBag doesn't do iterations.
// * SecretBag doesn't do iteations.
// * Nested SafeContents _can_ do iterations, but Windows ignores it. So we will ignore it too.
if (bag.BagId == Oids.Pkcs12ShroudedKeyBag)
{
AsnValueReader pkcs8ShroudedKeyReader = new AsnValueReader(bag.BagValue.Span, AsnEncodingRules.BER);
EncryptedPrivateKeyInfoAsn.Decode(
ref pkcs8ShroudedKeyReader,
bag.BagValue,
out EncryptedPrivateKeyInfoAsn epki);
count += IterationsFromParameters(epki.EncryptionAlgorithm);
}
}
}
finally
{
if (rentedData.HasValue)
{
CryptoPool.Return(rentedData.Value);
}
}
}
if (MacData.HasValue)
{
if (MacData.Value.IterationCount < 0)
{
throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding);
}
count += (uint)MacData.Value.IterationCount;
}
return count;
}
}
private static ArraySegment<byte> DecryptContentInfo(ContentInfoAsn contentInfo, out uint iterations)
{
EncryptedDataAsn encryptedData = EncryptedDataAsn.Decode(contentInfo.Content, AsnEncodingRules.BER);
if (encryptedData.Version != 0 && encryptedData.Version != 2)
{
throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding);
}
// The encrypted contentInfo can only wrap a PKCS7 data.
if (encryptedData.EncryptedContentInfo.ContentType != Oids.Pkcs7Data)
{
throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding);
}
if (!encryptedData.EncryptedContentInfo.EncryptedContent.HasValue)
{
throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding);
}
iterations = IterationsFromParameters(encryptedData.EncryptedContentInfo.ContentEncryptionAlgorithm);
// This encryptData is encrypted with more rounds than we are willing to process. Bail out of the whole thing.
if (iterations > MaxIterationWork)
{
throw new CryptographicException(SR.Cryptography_X509_PfxWithoutPassword);
}
int encryptedValueLength = encryptedData.EncryptedContentInfo.EncryptedContent.Value.Length;
byte[] destination = CryptoPool.Rent(encryptedValueLength);
int written = 0;
try
{
try
{
written = PasswordBasedEncryption.Decrypt(
in encryptedData.EncryptedContentInfo.ContentEncryptionAlgorithm,
EmptyPassword,
default,
encryptedData.EncryptedContentInfo.EncryptedContent.Value.Span,
destination);
}
catch
{
// If empty password didn't work, try null password.
written = PasswordBasedEncryption.Decrypt(
in encryptedData.EncryptedContentInfo.ContentEncryptionAlgorithm,
NullPassword,
default,
encryptedData.EncryptedContentInfo.EncryptedContent.Value.Span,
destination);
}
}
finally
{
if (written == 0)
{
// This means the decryption operation failed and destination could contain
// partial data. Clear it to be hygienic.
CryptographicOperations.ZeroMemory(destination);
}
}
return new ArraySegment<byte>(destination, 0, written);
}
private static uint IterationsFromParameters(in AlgorithmIdentifierAsn algorithmIdentifier)
{
switch (algorithmIdentifier.Algorithm)
{
case Oids.PasswordBasedEncryptionScheme2:
if (!algorithmIdentifier.Parameters.HasValue)
{
throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding);
}
PBES2Params pbes2Params = PBES2Params.Decode(algorithmIdentifier.Parameters.Value, AsnEncodingRules.BER);
// PBES2 only defines PKBDF2 for now. See RFC 8018 A.4
if (pbes2Params.KeyDerivationFunc.Algorithm != Oids.Pbkdf2)
{
throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding);
}
if (!pbes2Params.KeyDerivationFunc.Parameters.HasValue)
{
throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding);
}
Pbkdf2Params pbkdf2Params = Pbkdf2Params.Decode(pbes2Params.KeyDerivationFunc.Parameters.Value, AsnEncodingRules.BER);
if (pbkdf2Params.IterationCount < 0)
{
throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding);
}
return (uint)pbkdf2Params.IterationCount;
// PBES1
case Oids.PbeWithMD5AndDESCBC:
case Oids.PbeWithMD5AndRC2CBC:
case Oids.PbeWithSha1AndDESCBC:
case Oids.PbeWithSha1AndRC2CBC:
case Oids.Pkcs12PbeWithShaAnd3Key3Des:
case Oids.Pkcs12PbeWithShaAnd2Key3Des:
case Oids.Pkcs12PbeWithShaAnd128BitRC2:
case Oids.Pkcs12PbeWithShaAnd40BitRC2:
if (!algorithmIdentifier.Parameters.HasValue)
{
throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding);
}
PBEParameter pbeParameters = PBEParameter.Decode(
algorithmIdentifier.Parameters.Value,
AsnEncodingRules.BER);
if (pbeParameters.IterationCount < 0)
{
throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding);
}
return (uint)pbeParameters.IterationCount;
default:
throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding);
}
}
}
}
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
namespace System
{
internal static partial class LocalAppContextSwitches
{
internal const long DefaultPkcs12UnspecifiedPasswordIterationLimit = 600_000;
internal static long Pkcs12UnspecifiedPasswordIterationLimit { get; } = InitializePkcs12UnspecifiedPasswordIterationLimit();
private static long InitializePkcs12UnspecifiedPasswordIterationLimit()
{
object? data = AppContext.GetData("System.Security.Cryptography.Pkcs12UnspecifiedPasswordIterationLimit");
if (data is null)
{
return DefaultPkcs12UnspecifiedPasswordIterationLimit;
}
try
{
return Convert.ToInt64(data);
}
catch
{
return DefaultPkcs12UnspecifiedPasswordIterationLimit;
}
}
}
}
...@@ -5,10 +5,13 @@ ...@@ -5,10 +5,13 @@
using Internal.Cryptography.Pal; using Internal.Cryptography.Pal;
using Microsoft.Win32.SafeHandles; using Microsoft.Win32.SafeHandles;
using System; using System;
using System.Buffers;
using System.Diagnostics; using System.Diagnostics;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.Formats.Asn1;
using System.Globalization; using System.Globalization;
using System.Runtime.Serialization; using System.Runtime.Serialization;
using System.Security.Cryptography.Asn1.Pkcs12;
using System.Text; using System.Text;
namespace System.Security.Cryptography.X509Certificates namespace System.Security.Cryptography.X509Certificates
...@@ -61,7 +64,7 @@ private protected X509Certificate(ReadOnlySpan<byte> data) ...@@ -61,7 +64,7 @@ private protected X509Certificate(ReadOnlySpan<byte> data)
if (!data.IsEmpty) if (!data.IsEmpty)
{ {
// For compat reasons, this constructor treats passing a null or empty data set as the same as calling the nullary constructor. // For compat reasons, this constructor treats passing a null or empty data set as the same as calling the nullary constructor.
using (var safePasswordHandle = new SafePasswordHandle((string?)null)) using (var safePasswordHandle = new SafePasswordHandle((string?)null, passwordProvided: false))
{ {
Pal = CertificatePal.FromBlob(data, safePasswordHandle, X509KeyStorageFlags.DefaultKeySet); Pal = CertificatePal.FromBlob(data, safePasswordHandle, X509KeyStorageFlags.DefaultKeySet);
} }
...@@ -86,7 +89,7 @@ public X509Certificate(byte[] rawData, string? password, X509KeyStorageFlags key ...@@ -86,7 +89,7 @@ public X509Certificate(byte[] rawData, string? password, X509KeyStorageFlags key
ValidateKeyStorageFlags(keyStorageFlags); ValidateKeyStorageFlags(keyStorageFlags);
using (var safePasswordHandle = new SafePasswordHandle(password)) using (var safePasswordHandle = new SafePasswordHandle(password, passwordProvided: true))
{ {
Pal = CertificatePal.FromBlob(rawData, safePasswordHandle, keyStorageFlags); Pal = CertificatePal.FromBlob(rawData, safePasswordHandle, keyStorageFlags);
} }
...@@ -100,7 +103,7 @@ public X509Certificate(byte[] rawData, SecureString? password, X509KeyStorageFla ...@@ -100,7 +103,7 @@ public X509Certificate(byte[] rawData, SecureString? password, X509KeyStorageFla
ValidateKeyStorageFlags(keyStorageFlags); ValidateKeyStorageFlags(keyStorageFlags);
using (var safePasswordHandle = new SafePasswordHandle(password)) using (var safePasswordHandle = new SafePasswordHandle(password, passwordProvided: true))
{ {
Pal = CertificatePal.FromBlob(rawData, safePasswordHandle, keyStorageFlags); Pal = CertificatePal.FromBlob(rawData, safePasswordHandle, keyStorageFlags);
} }
...@@ -113,7 +116,7 @@ private protected X509Certificate(ReadOnlySpan<byte> rawData, ReadOnlySpan<char> ...@@ -113,7 +116,7 @@ private protected X509Certificate(ReadOnlySpan<byte> rawData, ReadOnlySpan<char>
ValidateKeyStorageFlags(keyStorageFlags); ValidateKeyStorageFlags(keyStorageFlags);
using (var safePasswordHandle = new SafePasswordHandle(password)) using (var safePasswordHandle = new SafePasswordHandle(password, passwordProvided: true))
{ {
Pal = CertificatePal.FromBlob(rawData, safePasswordHandle, keyStorageFlags); Pal = CertificatePal.FromBlob(rawData, safePasswordHandle, keyStorageFlags);
} }
...@@ -153,7 +156,7 @@ public X509Certificate(string fileName, string? password, X509KeyStorageFlags ke ...@@ -153,7 +156,7 @@ public X509Certificate(string fileName, string? password, X509KeyStorageFlags ke
ValidateKeyStorageFlags(keyStorageFlags); ValidateKeyStorageFlags(keyStorageFlags);
using (var safePasswordHandle = new SafePasswordHandle(password)) using (var safePasswordHandle = new SafePasswordHandle(password, passwordProvided: true))
{ {
Pal = CertificatePal.FromFile(fileName, safePasswordHandle, keyStorageFlags); Pal = CertificatePal.FromFile(fileName, safePasswordHandle, keyStorageFlags);
} }
...@@ -166,7 +169,7 @@ private protected X509Certificate(string fileName, ReadOnlySpan<char> password, ...@@ -166,7 +169,7 @@ private protected X509Certificate(string fileName, ReadOnlySpan<char> password,
ValidateKeyStorageFlags(keyStorageFlags); ValidateKeyStorageFlags(keyStorageFlags);
using (var safePasswordHandle = new SafePasswordHandle(password)) using (var safePasswordHandle = new SafePasswordHandle(password, passwordProvided: true))
{ {
Pal = CertificatePal.FromFile(fileName, safePasswordHandle, keyStorageFlags); Pal = CertificatePal.FromFile(fileName, safePasswordHandle, keyStorageFlags);
} }
...@@ -182,7 +185,7 @@ public X509Certificate(string fileName, SecureString? password, X509KeyStorageFl ...@@ -182,7 +185,7 @@ public X509Certificate(string fileName, SecureString? password, X509KeyStorageFl
ValidateKeyStorageFlags(keyStorageFlags); ValidateKeyStorageFlags(keyStorageFlags);
using (var safePasswordHandle = new SafePasswordHandle(password)) using (var safePasswordHandle = new SafePasswordHandle(password, passwordProvided: true))
{ {
Pal = CertificatePal.FromFile(fileName, safePasswordHandle, keyStorageFlags); Pal = CertificatePal.FromFile(fileName, safePasswordHandle, keyStorageFlags);
} }
...@@ -321,7 +324,7 @@ public virtual byte[] Export(X509ContentType contentType, string? password) ...@@ -321,7 +324,7 @@ public virtual byte[] Export(X509ContentType contentType, string? password)
if (Pal == null) if (Pal == null)
throw new CryptographicException(ErrorCode.E_POINTER); // Not the greatest error, but needed for backward compat. throw new CryptographicException(ErrorCode.E_POINTER); // Not the greatest error, but needed for backward compat.
using (var safePasswordHandle = new SafePasswordHandle(password)) using (var safePasswordHandle = new SafePasswordHandle(password, passwordProvided: true))
{ {
return Pal.Export(contentType, safePasswordHandle); return Pal.Export(contentType, safePasswordHandle);
} }
...@@ -335,7 +338,7 @@ public virtual byte[] Export(X509ContentType contentType, SecureString? password ...@@ -335,7 +338,7 @@ public virtual byte[] Export(X509ContentType contentType, SecureString? password
if (Pal == null) if (Pal == null)
throw new CryptographicException(ErrorCode.E_POINTER); // Not the greatest error, but needed for backward compat. throw new CryptographicException(ErrorCode.E_POINTER); // Not the greatest error, but needed for backward compat.
using (var safePasswordHandle = new SafePasswordHandle(password)) using (var safePasswordHandle = new SafePasswordHandle(password, passwordProvided: true))
{ {
return Pal.Export(contentType, safePasswordHandle); return Pal.Export(contentType, safePasswordHandle);
} }
...@@ -694,6 +697,88 @@ private void VerifyContentType(X509ContentType contentType) ...@@ -694,6 +697,88 @@ private void VerifyContentType(X509ContentType contentType)
throw new CryptographicException(SR.Cryptography_X509_InvalidContentType); throw new CryptographicException(SR.Cryptography_X509_InvalidContentType);
} }
internal static void EnforceIterationCountLimit(ReadOnlySpan<byte> pkcs12, bool readingFromFile, bool passwordProvided)
{
if (readingFromFile || passwordProvided)
{
return;
}
long pkcs12UnspecifiedPasswordIterationLimit = LocalAppContextSwitches.Pkcs12UnspecifiedPasswordIterationLimit;
// -1 = no limit
if (LocalAppContextSwitches.Pkcs12UnspecifiedPasswordIterationLimit == -1)
{
return;
}
// any other negative number means use default limits
if (pkcs12UnspecifiedPasswordIterationLimit < 0)
{
pkcs12UnspecifiedPasswordIterationLimit = LocalAppContextSwitches.DefaultPkcs12UnspecifiedPasswordIterationLimit;
}
try
{
try
{
checked
{
KdfWorkLimiter.SetIterationLimit((ulong)pkcs12UnspecifiedPasswordIterationLimit);
ulong observedIterationCount = GetIterationCount(pkcs12);
// Check both conditions: we want a KDF-exceeded failure anywhere in the system to produce a failure here.
// There are some places within the GetIterationCount method where we optimistically try processing the
// PFX in one manner, and if we see failures we'll swallow any exceptions and try a different manner
// instead. The problem with this is that when we swallow failures, we don't have the ability to add the
// so-far-observed iteration count back to the running total returned by GetIterationCount. This
// potentially allows a clever adversary a window through which to squeeze in work beyond our configured
// limits. To mitigate this risk, we'll fail now if we observed *any* KDF-exceeded failure while processing
// this PFX.
if (observedIterationCount > (ulong)pkcs12UnspecifiedPasswordIterationLimit || KdfWorkLimiter.WasWorkLimitExceeded())
{
throw new CryptographicException(); // iteration count exceeded
}
}
}
finally
{
KdfWorkLimiter.ResetIterationLimit();
}
}
catch (Exception ex)
{
// It's important for this catch-all block to be *outside* the inner try/finally
// so that we can prevent exception filters from running before we've had a chance
// to clean up the threadstatic.
throw new CryptographicException(SR.Cryptography_X509_PfxWithoutPassword, ex);
}
}
internal static ulong GetIterationCount(ReadOnlySpan<byte> pkcs12)
{
ulong iterations;
unsafe
{
fixed (byte* pin = pkcs12)
{
using (var manager = new PointerMemoryManager<byte>(pin, pkcs12.Length))
{
AsnValueReader reader = new AsnValueReader(pkcs12, AsnEncodingRules.BER);
PfxAsn.Decode(ref reader, manager.Memory, out PfxAsn pfx);
// Don't throw when trailing data is present.
// Windows doesn't have such enforcement as well.
iterations = pfx.CountTotalIterations();
}
}
}
return iterations;
}
internal const X509KeyStorageFlags KeyStorageFlagsAll = internal const X509KeyStorageFlags KeyStorageFlagsAll =
X509KeyStorageFlags.UserKeySet | X509KeyStorageFlags.UserKeySet |
X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.MachineKeySet |
......
...@@ -109,12 +109,16 @@ public bool Contains(X509Certificate2 certificate) ...@@ -109,12 +109,16 @@ public bool Contains(X509Certificate2 certificate)
public byte[]? Export(X509ContentType contentType) public byte[]? Export(X509ContentType contentType)
{ {
return Export(contentType, password: null); using (var safePasswordHandle = new SafePasswordHandle((string?)null, passwordProvided: false))
using (IExportPal storePal = StorePal.LinkFromCertificateCollection(this))
{
return storePal.Export(contentType, safePasswordHandle);
}
} }
public byte[]? Export(X509ContentType contentType, string? password) public byte[]? Export(X509ContentType contentType, string? password)
{ {
using (var safePasswordHandle = new SafePasswordHandle(password)) using (var safePasswordHandle = new SafePasswordHandle(password, passwordProvided: true))
using (IExportPal storePal = StorePal.LinkFromCertificateCollection(this)) using (IExportPal storePal = StorePal.LinkFromCertificateCollection(this))
{ {
return storePal.Export(contentType, safePasswordHandle); return storePal.Export(contentType, safePasswordHandle);
...@@ -152,7 +156,11 @@ public void Import(byte[] rawData) ...@@ -152,7 +156,11 @@ public void Import(byte[] rawData)
/// </param> /// </param>
public void Import(ReadOnlySpan<byte> rawData) public void Import(ReadOnlySpan<byte> rawData)
{ {
Import(rawData, password: null, keyStorageFlags: X509KeyStorageFlags.DefaultKeySet); using (var safePasswordHandle = new SafePasswordHandle((string?)null, passwordProvided: false))
using (ILoaderPal storePal = StorePal.FromBlob(rawData, safePasswordHandle, X509KeyStorageFlags.DefaultKeySet))
{
storePal.MoveTo(this);
}
} }
public void Import(byte[] rawData, string? password, X509KeyStorageFlags keyStorageFlags = 0) public void Import(byte[] rawData, string? password, X509KeyStorageFlags keyStorageFlags = 0)
...@@ -199,7 +207,7 @@ public void Import(ReadOnlySpan<byte> rawData, ReadOnlySpan<char> password, X509 ...@@ -199,7 +207,7 @@ public void Import(ReadOnlySpan<byte> rawData, ReadOnlySpan<char> password, X509
X509Certificate.ValidateKeyStorageFlags(keyStorageFlags); X509Certificate.ValidateKeyStorageFlags(keyStorageFlags);
using (var safePasswordHandle = new SafePasswordHandle(password)) using (var safePasswordHandle = new SafePasswordHandle(password, passwordProvided: true))
using (ILoaderPal storePal = StorePal.FromBlob(rawData, safePasswordHandle, keyStorageFlags)) using (ILoaderPal storePal = StorePal.FromBlob(rawData, safePasswordHandle, keyStorageFlags))
{ {
storePal.MoveTo(this); storePal.MoveTo(this);
...@@ -208,7 +216,14 @@ public void Import(ReadOnlySpan<byte> rawData, ReadOnlySpan<char> password, X509 ...@@ -208,7 +216,14 @@ public void Import(ReadOnlySpan<byte> rawData, ReadOnlySpan<char> password, X509
public void Import(string fileName) public void Import(string fileName)
{ {
Import(fileName, password: null, keyStorageFlags: X509KeyStorageFlags.DefaultKeySet); if (fileName == null)
throw new ArgumentNullException(nameof(fileName));
using (var safePasswordHandle = new SafePasswordHandle((string?)null, passwordProvided: false))
using (ILoaderPal storePal = StorePal.FromFile(fileName, safePasswordHandle, X509KeyStorageFlags.DefaultKeySet))
{
storePal.MoveTo(this);
}
} }
public void Import(string fileName, string? password, X509KeyStorageFlags keyStorageFlags = 0) public void Import(string fileName, string? password, X509KeyStorageFlags keyStorageFlags = 0)
...@@ -218,7 +233,7 @@ public void Import(string fileName, string? password, X509KeyStorageFlags keySto ...@@ -218,7 +233,7 @@ public void Import(string fileName, string? password, X509KeyStorageFlags keySto
X509Certificate.ValidateKeyStorageFlags(keyStorageFlags); X509Certificate.ValidateKeyStorageFlags(keyStorageFlags);
using (var safePasswordHandle = new SafePasswordHandle(password)) using (var safePasswordHandle = new SafePasswordHandle(password, passwordProvided: true))
using (ILoaderPal storePal = StorePal.FromFile(fileName, safePasswordHandle, keyStorageFlags)) using (ILoaderPal storePal = StorePal.FromFile(fileName, safePasswordHandle, keyStorageFlags))
{ {
storePal.MoveTo(this); storePal.MoveTo(this);
...@@ -244,7 +259,7 @@ public void Import(string fileName, ReadOnlySpan<char> password, X509KeyStorageF ...@@ -244,7 +259,7 @@ public void Import(string fileName, ReadOnlySpan<char> password, X509KeyStorageF
X509Certificate.ValidateKeyStorageFlags(keyStorageFlags); X509Certificate.ValidateKeyStorageFlags(keyStorageFlags);
using (var safePasswordHandle = new SafePasswordHandle(password)) using (var safePasswordHandle = new SafePasswordHandle(password, passwordProvided: true))
using (ILoaderPal storePal = StorePal.FromFile(fileName, safePasswordHandle, keyStorageFlags)) using (ILoaderPal storePal = StorePal.FromFile(fileName, safePasswordHandle, keyStorageFlags))
{ {
storePal.MoveTo(this); storePal.MoveTo(this);
......
...@@ -411,7 +411,7 @@ public static void ExportPublicKeyAsPkcs12() ...@@ -411,7 +411,7 @@ public static void ExportPublicKeyAsPkcs12()
// Read it back as a collection, there should be only one cert, and it should // Read it back as a collection, there should be only one cert, and it should
// be equal to the one we started with. // be equal to the one we started with.
using (ImportedCollection ic = Cert.Import(pkcs12Bytes)) using (ImportedCollection ic = Cert.Import(pkcs12Bytes, (string?)null, X509KeyStorageFlags.DefaultKeySet))
{ {
X509Certificate2Collection fromPfx = ic.Collection; X509Certificate2Collection fromPfx = ic.Collection;
......
...@@ -22,7 +22,7 @@ public static void ImportNull() ...@@ -22,7 +22,7 @@ public static void ImportNull()
[Fact] [Fact]
public static void ImportEmpty_Pkcs12() public static void ImportEmpty_Pkcs12()
{ {
using (ImportedCollection ic = Cert.Import(TestData.EmptyPfx)) using (ImportedCollection ic = Cert.Import(TestData.EmptyPfx, (string?)null, X509KeyStorageFlags.DefaultKeySet))
{ {
X509Certificate2Collection collection = ic.Collection; X509Certificate2Collection collection = ic.Collection;
Assert.Equal(0, collection.Count); Assert.Equal(0, collection.Count);
......
...@@ -635,7 +635,7 @@ public static void ImportFromFileTests(X509KeyStorageFlags storageFlags) ...@@ -635,7 +635,7 @@ public static void ImportFromFileTests(X509KeyStorageFlags storageFlags)
[Fact] [Fact]
public static void ImportMultiplePrivateKeysPfx() public static void ImportMultiplePrivateKeysPfx()
{ {
using (ImportedCollection ic = Cert.Import(TestData.MultiPrivateKeyPfx)) using (ImportedCollection ic = Cert.Import(TestData.MultiPrivateKeyPfx, (string?)null, X509KeyStorageFlags.DefaultKeySet))
{ {
X509Certificate2Collection collection = ic.Collection; X509Certificate2Collection collection = ic.Collection;
...@@ -751,7 +751,7 @@ public static void ExportUnrelatedPfx() ...@@ -751,7 +751,7 @@ public static void ExportUnrelatedPfx()
byte[] exported = collection.Export(X509ContentType.Pkcs12); byte[] exported = collection.Export(X509ContentType.Pkcs12);
using (ImportedCollection ic = Cert.Import(exported)) using (ImportedCollection ic = Cert.Import(exported, (string?)null, X509KeyStorageFlags.DefaultKeySet))
{ {
X509Certificate2Collection importedCollection = ic.Collection; X509Certificate2Collection importedCollection = ic.Collection;
...@@ -813,7 +813,7 @@ public static void ExportMultiplePrivateKeys() ...@@ -813,7 +813,7 @@ public static void ExportMultiplePrivateKeys()
byte[] exported = collection.Export(X509ContentType.Pkcs12); byte[] exported = collection.Export(X509ContentType.Pkcs12);
using (ImportedCollection ic = Cert.Import(exported)) using (ImportedCollection ic = Cert.Import(exported, (string?)null, X509KeyStorageFlags.DefaultKeySet))
{ {
X509Certificate2Collection importedCollection = ic.Collection; X509Certificate2Collection importedCollection = ic.Collection;
...@@ -852,7 +852,7 @@ public static void CanAddMultipleCertsWithSinglePrivateKey() ...@@ -852,7 +852,7 @@ public static void CanAddMultipleCertsWithSinglePrivateKey()
byte[] buffer = col.Export(X509ContentType.Pfx); byte[] buffer = col.Export(X509ContentType.Pfx);
using (ImportedCollection newCollection = Cert.Import(buffer)) using (ImportedCollection newCollection = Cert.Import(buffer, (string?)null, X509KeyStorageFlags.DefaultKeySet))
{ {
Assert.Equal(2, newCollection.Collection.Count); Assert.Equal(2, newCollection.Collection.Count);
} }
......
...@@ -65,7 +65,7 @@ public static void ExportAsPfx() ...@@ -65,7 +65,7 @@ public static void ExportAsPfx()
byte[] pfx = c1.Export(X509ContentType.Pkcs12); byte[] pfx = c1.Export(X509ContentType.Pkcs12);
Assert.Equal(X509ContentType.Pkcs12, X509Certificate2.GetCertContentType(pfx)); Assert.Equal(X509ContentType.Pkcs12, X509Certificate2.GetCertContentType(pfx));
using (X509Certificate2 c2 = new X509Certificate2(pfx)) using (X509Certificate2 c2 = new X509Certificate2(pfx, (string?)null))
{ {
byte[] rawData = c2.Export(X509ContentType.Cert); byte[] rawData = c2.Export(X509ContentType.Cert);
Assert.Equal(TestData.MsCertificate, rawData); Assert.Equal(TestData.MsCertificate, rawData);
...@@ -134,7 +134,7 @@ public static void ExportAsPfxWithPrivateKey() ...@@ -134,7 +134,7 @@ public static void ExportAsPfxWithPrivateKey()
byte[] pfxBytes = cert.Export(X509ContentType.Pkcs12); byte[] pfxBytes = cert.Export(X509ContentType.Pkcs12);
using (X509Certificate2 fromPfx = new X509Certificate2(pfxBytes)) using (X509Certificate2 fromPfx = new X509Certificate2(pfxBytes, (string?)null))
{ {
Assert.Equal(cert, fromPfx); Assert.Equal(cert, fromPfx);
Assert.True(fromPfx.HasPrivateKey, "fromPfx.HasPrivateKey"); Assert.True(fromPfx.HasPrivateKey, "fromPfx.HasPrivateKey");
......
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using Microsoft.DotNet.RemoteExecutor;
using Microsoft.DotNet.XUnitExtensions;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography.X509Certificates;
using Xunit;
namespace System.Security.Cryptography.X509Certificates.Tests
{
// AppContext and AppDomain are the same in this context.
public class PfxIterationCountTests_CustomAppDomainDataLimit
{
// We need to use virtual in a non-abstract class because RemoteExecutor can't work on abstract classes.
internal virtual X509Certificate Import(byte[] blob) => new X509Certificate(blob);
[ConditionalTheory(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))]
[MemberData(memberName: nameof(PfxIterationCountTests.GetCertsWith_IterationCountNotExceedingDefaultLimit_AndNullOrEmptyPassword_MemberData), MemberType = typeof(PfxIterationCountTests))]
public void Import_AppDomainDataWithValueTwo_ActsAsDefaultLimit_IterationCountNotExceedingDefaultLimit(string name, bool usesPbes2, byte[] blob, long iterationCount)
{
_ = iterationCount;
_ = blob;
if (usesPbes2 && !PfxTests.Pkcs12PBES2Supported)
{
throw new SkipTestException(name + " uses PBES2 which is not supported on this version.");
}
RemoteExecutor.Invoke((certName) =>
{
AppDomain.CurrentDomain.SetData("System.Security.Cryptography.Pkcs12UnspecifiedPasswordIterationLimit", -2);
PfxInfo pfxInfo = s_certificatesDictionary[certName];
X509Certificate cert = Import(pfxInfo.Blob);
Assert.True(cert.Subject == "CN=test" || cert.Subject == "CN=potato");
}, name).Dispose();
}
[ConditionalTheory(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))]
[MemberData(memberName: nameof(PfxIterationCountTests.GetCertsWith_IterationCountExceedingDefaultLimit_MemberData), MemberType = typeof(PfxIterationCountTests))]
public void Import_AppDomainDataWithValueTwo_ActsAsDefaultLimit_IterationCountLimitExceeded_Throws(string name, string password, bool usesPbes2, byte[] blob, long iterationCount)
{
_ = password;
_ = iterationCount;
_ = blob;
if (usesPbes2 && !PfxTests.Pkcs12PBES2Supported)
{
throw new SkipTestException(name + " uses PBES2 which is not supported on this version.");
}
RemoteExecutor.Invoke((certName) =>
{
AppDomain.CurrentDomain.SetData("System.Security.Cryptography.Pkcs12UnspecifiedPasswordIterationLimit", -2);
PfxInfo pfxInfo = s_certificatesDictionary[certName];
CryptographicException ce = Assert.Throws<CryptographicException>(() => Import(pfxInfo.Blob));
Assert.Contains("2233907", ce.Message);
}, name).Dispose();
}
[ConditionalTheory(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))]
[MemberData(memberName: nameof(PfxIterationCountTests.GetCertsWith_IterationCountNotExceedingDefaultLimit_AndNullOrEmptyPassword_MemberData), MemberType = typeof(PfxIterationCountTests))]
public void Import_AppDomainDataWithValueZero_IterationCountNotExceedingDefaultLimit_Throws(string name, bool usesPbes2, byte[] blob, long iterationCount)
{
_ = iterationCount;
_ = blob;
if (usesPbes2 && !PfxTests.Pkcs12PBES2Supported)
{
throw new SkipTestException(name + " uses PBES2 which is not supported on this version.");
}
RemoteExecutor.Invoke((certName) =>
{
AppDomain.CurrentDomain.SetData("System.Security.Cryptography.Pkcs12UnspecifiedPasswordIterationLimit", 0);
PfxInfo pfxInfo = s_certificatesDictionary[certName];
CryptographicException ce = Assert.Throws<CryptographicException>(() => Import(pfxInfo.Blob));
Assert.Contains("2233907", ce.Message);
}, name).Dispose();
}
[ConditionalTheory(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))]
[MemberData(memberName: nameof(PfxIterationCountTests.GetCertsWith_IterationCountExceedingDefaultLimit_MemberData), MemberType = typeof(PfxIterationCountTests))]
public void Import_AppDomainDataWithValueMinusOne_IterationCountExceedingDefaultLimit(string name, string password, bool usesPbes2, byte[] blob, long iterationCount)
{
_ = password;
_ = blob;
_ = iterationCount;
if (usesPbes2 && !PfxTests.Pkcs12PBES2Supported)
{
throw new SkipTestException(name + " uses PBES2 which is not supported on this version.");
}
RemoteExecutor.Invoke((certName) =>
{
AppDomain.CurrentDomain.SetData("System.Security.Cryptography.Pkcs12UnspecifiedPasswordIterationLimit", -1);
PfxInfo pfxInfo = s_certificatesDictionary[certName];
if (OperatingSystem.IsWindows())
{
// Opting-out with AppDomain data value -1 will still give us error because cert is beyond Windows limit.
// But we will get the CryptoThrowHelper+WindowsCryptographicException.
PfxIterationCountTests.VerifyThrowsCryptoExButDoesNotThrowPfxWithoutPassword(() => Import(pfxInfo.Blob));
}
else
{
Assert.NotNull(Import(pfxInfo.Blob));
}
}, name).Dispose();
}
public static readonly Dictionary<string, PfxInfo> s_certificatesDictionary
= PfxIterationCountTests.s_Certificates.ToDictionary((c) => c.Name);
}
public class PfxIterationCountTests_CustomLimit_X509Certificate2 : PfxIterationCountTests_CustomAppDomainDataLimit
{
internal override X509Certificate Import(byte[] blob) => new X509Certificate2(blob);
}
public class PfxIterationCountTests_CustomLimit_X509Certificate2Collection : PfxIterationCountTests_CustomAppDomainDataLimit
{
internal override X509Certificate Import(byte[] blob)
{
X509Certificate2Collection collection = new X509Certificate2Collection();
collection.Import(blob);
return collection[0];
}
}
}
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
namespace System.Security.Cryptography.X509Certificates.Tests
{
public class PfxIterationCountTests_X509Certificate : PfxIterationCountTests
{
internal override X509Certificate Import(byte[] blob)
=> new X509Certificate(blob);
internal override X509Certificate Import(byte[] blob, string password)
=> new X509Certificate(blob, password);
internal override X509Certificate Import(byte[] blob, SecureString password)
=> new X509Certificate(blob, password);
internal override X509Certificate Import(string fileName)
=> new X509Certificate(fileName);
internal override X509Certificate Import(string fileName, string password)
=> new X509Certificate(fileName, password);
internal override X509Certificate Import(string fileName, SecureString password)
=> new X509Certificate(fileName, password);
}
}
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
namespace System.Security.Cryptography.X509Certificates.Tests
{
public class PfxIterationCountTests_X509Certificate2 : PfxIterationCountTests
{
internal override X509Certificate Import(byte[] blob)
=> new X509Certificate2(blob);
internal override X509Certificate Import(byte[] blob, string password)
=> new X509Certificate2(blob, password);
internal override X509Certificate Import(byte[] blob, SecureString password)
=> new X509Certificate2(blob, password);
internal override X509Certificate Import(string fileName)
=> new X509Certificate2(fileName);
internal override X509Certificate Import(string fileName, string password)
=> new X509Certificate2(fileName, password);
internal override X509Certificate Import(string fileName, SecureString password)
=> new X509Certificate2(fileName, password);
}
}
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
namespace System.Security.Cryptography.X509Certificates.Tests
{
public class PfxIterationCountTests_X509Certificate2Collection : PfxIterationCountTests
{
internal override X509Certificate Import(byte[] blob)
{
X509Certificate2Collection collection = new X509Certificate2Collection();
collection.Import(blob);
return collection[0];
}
internal override X509Certificate Import(byte[] blob, string password)
{
X509Certificate2Collection collection = new X509Certificate2Collection();
collection.Import(blob, password, X509KeyStorageFlags.DefaultKeySet);
return collection[0];
}
// X509Certificate2Collection.Import does not support SecureString so we just make this work.
internal override X509Certificate Import(byte[] blob, SecureString password)
=> new X509Certificate2(blob, password);
internal override X509Certificate Import(string fileName)
{
X509Certificate2Collection collection = new X509Certificate2Collection();
collection.Import(fileName);
return collection[0];
}
internal override X509Certificate Import(string fileName, string password)
{
X509Certificate2Collection collection = new X509Certificate2Collection();
collection.Import(fileName, password, X509KeyStorageFlags.DefaultKeySet);
return collection[0];
}
// X509Certificate2Collection.Import does not support SecureString so we just make this work.
internal override X509Certificate Import(string fileName, SecureString password)
=> new X509Certificate2(fileName, password);
}
}
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using Microsoft.DotNet.XUnitExtensions;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography.X509Certificates;
using Test.Cryptography;
using Xunit;
namespace System.Security.Cryptography.X509Certificates.Tests
{
public abstract partial class PfxIterationCountTests
{
private const long DefaultIterationLimit = 600_000;
internal abstract X509Certificate Import(byte[] blob);
internal abstract X509Certificate Import(byte[] blob, string password);
internal abstract X509Certificate Import(byte[] blob, SecureString password);
internal abstract X509Certificate Import(string fileName);
internal abstract X509Certificate Import(string fileName, string password);
internal abstract X509Certificate Import(string fileName, SecureString password);
[ConditionalTheory]
[MemberData(nameof(GetCertsWith_IterationCountNotExceedingDefaultLimit_AndNullOrEmptyPassword_MemberData))]
public void Import_IterationCounLimitNotExceeded_Succeeds(string name, bool usesPbes2, byte[] blob, long iterationCount)
{
if (usesPbes2 && !PfxTests.Pkcs12PBES2Supported)
{
throw new SkipTestException(name + " uses PBES2 which is not supported on this version.");
}
if (PfxTests.IsPkcs12IterationCountAllowed(iterationCount, PfxTests.DefaultIterations))
{
X509Certificate cert = Import(blob);
Assert.True(cert.Subject == "CN=test" || cert.Subject == "CN=potato");
}
}
[ConditionalTheory]
[MemberData(nameof(GetCertsWith_IterationCountExceedingDefaultLimit_MemberData))]
public void Import_IterationCountLimitExceeded_Throws(string name, string password, bool usesPbes2, byte[] blob, long iterationCount)
{
_ = password;
_ = iterationCount;
if (usesPbes2 && !PfxTests.Pkcs12PBES2Supported)
{
throw new SkipTestException(name + " uses PBES2 which is not supported on this version.");
}
CryptographicException ce = Assert.Throws<CryptographicException>(() => Import(blob));
Assert.Contains("2233907", ce.Message);
}
[ConditionalTheory]
[MemberData(nameof(GetCertsWith_IterationCountExceedingDefaultLimit_MemberData))]
public void ImportWithPasswordOrFileName_IterationCountLimitExceeded(string name, string password, bool usesPbes2, byte[] blob, long iterationCount)
{
if (usesPbes2 && !PfxTests.Pkcs12PBES2Supported)
{
throw new SkipTestException(name + " uses PBES2 which is not supported on this version.");
}
using (TempFileHolder tempFile = new TempFileHolder(blob))
{
string fileName = tempFile.FilePath;
if (PfxTests.IsPkcs12IterationCountAllowed(iterationCount, PfxTests.DefaultIterations))
{
Assert.NotNull(Import(blob, password));
Assert.NotNull(Import(blob, PfxTests.GetSecureString(password)));
Assert.NotNull(Import(fileName));
Assert.NotNull(Import(fileName, password));
Assert.NotNull(Import(fileName, PfxTests.GetSecureString(password)));
}
else
{
if (OperatingSystem.IsWindows())
{
// Specifying password or importing from file will still give us error because cert is beyond Windows limit.
// But we will get the CryptoThrowHelper+WindowsCryptographicException.
VerifyThrowsCryptoExButDoesNotThrowPfxWithoutPassword(() => Import(blob, password));
VerifyThrowsCryptoExButDoesNotThrowPfxWithoutPassword(() => Import(blob, PfxTests.GetSecureString(password)));
// Using a file will do as above as well.
VerifyThrowsCryptoExButDoesNotThrowPfxWithoutPassword(() => Import(fileName));
VerifyThrowsCryptoExButDoesNotThrowPfxWithoutPassword(() => Import(fileName, password));
VerifyThrowsCryptoExButDoesNotThrowPfxWithoutPassword(() => Import(fileName, PfxTests.GetSecureString(password)));
}
}
}
}
internal static void VerifyThrowsCryptoExButDoesNotThrowPfxWithoutPassword(Action action)
{
CryptographicException ce = Assert.ThrowsAny<CryptographicException>(action);
Assert.DoesNotContain("2233907", ce.Message);
}
[ConditionalTheory]
[MemberData(nameof(GetCertsWith_NonNullOrEmptyPassword_MemberData))]
public void Import_NonNullOrEmptyPasswordExpected_Throws(string name, string password, bool usesPbes2, byte[] blob, long iterationCount)
{
if (usesPbes2 && !PfxTests.Pkcs12PBES2Supported)
{
throw new SkipTestException(name + " uses PBES2 which is not supported on this version.");
}
CryptographicException ce = Assert.ThrowsAny<CryptographicException>(() => Import(blob));
if (PfxTests.IsPkcs12IterationCountAllowed(iterationCount, PfxTests.DefaultIterations))
{
Assert.NotNull(Import(blob, password));
Assert.NotNull(Import(blob, PfxTests.GetSecureString(password)));
using (TempFileHolder tempFile = new TempFileHolder(blob))
{
string fileName = tempFile.FilePath;
Assert.NotNull(Import(fileName, password));
Assert.NotNull(Import(fileName, PfxTests.GetSecureString(password)));
}
}
}
internal static readonly List<PfxInfo> s_Certificates = GetCertificates();
internal static List<PfxInfo> GetCertificates()
{
List<PfxInfo> certificates = new List<PfxInfo>();
certificates.Add(new PfxInfo(
nameof(TestData.Pkcs12NoPassword2048RoundsHex), null, 2048 * 3, true, TestData.Pkcs12NoPassword2048RoundsHex.HexToByteArray()));
certificates.Add(new PfxInfo(
nameof(TestData.Pkcs12OpenSslOneCertDefaultEmptyPassword), "", 2048 * 3, true, TestData.Pkcs12OpenSslOneCertDefaultEmptyPassword.HexToByteArray()));
certificates.Add(new PfxInfo(
nameof(TestData.Pkcs12OpenSslOneCertDefaultNoMac), null, 2048, true, TestData.Pkcs12OpenSslOneCertDefaultNoMac.HexToByteArray()));
certificates.Add(new PfxInfo(
nameof(TestData.Pkcs12NoPasswordRandomCounts), null, 938, true, TestData.Pkcs12NoPasswordRandomCounts));
certificates.Add(new PfxInfo(
nameof(TestData.Pkcs12WindowsDotnetExportEmptyPassword), "", 6000, false, TestData.Pkcs12WindowsDotnetExportEmptyPassword.HexToByteArray()));
certificates.Add(new PfxInfo(
nameof(TestData.Pkcs12MacosKeychainCreated), null, 4097, false, TestData.Pkcs12MacosKeychainCreated.HexToByteArray()));
certificates.Add(new PfxInfo(
nameof(TestData.Pkcs12BuilderSaltWithMacNullPassword), null, 120000, true, TestData.Pkcs12BuilderSaltWithMacNullPassword.HexToByteArray()));
certificates.Add(new PfxInfo(
nameof(TestData.Pkcs12Builder3DESCBCWithNullPassword), null, 30000, false, TestData.Pkcs12Builder3DESCBCWithNullPassword.HexToByteArray()));
certificates.Add(new PfxInfo(
nameof(TestData.Pkcs12Builder3DESCBCWithEmptyPassword), "", 30000, false, TestData.Pkcs12Builder3DESCBCWithEmptyPassword.HexToByteArray()));
certificates.Add(new PfxInfo(
nameof(TestData.Pkcs12WindowsWithCertPrivacyPasswordIsOne), "1", 4000, false, TestData.Pkcs12WindowsWithCertPrivacyPasswordIsOne.HexToByteArray()));
certificates.Add(new PfxInfo(
nameof(TestData.Pkcs12WindowsWithoutCertPrivacyPasswordIsOne), "1", 4000, false, TestData.Pkcs12WindowsWithoutCertPrivacyPasswordIsOne.HexToByteArray()));
certificates.Add(new PfxInfo(
nameof(TestData.Pkcs12NoPassword600KPlusOneRoundsHex), null, 600_001 * 3, true, TestData.Pkcs12NoPassword600KPlusOneRoundsHex.HexToByteArray()));
return certificates;
}
public static IEnumerable<object[]> GetCertsWith_IterationCountNotExceedingDefaultLimit_AndNullOrEmptyPassword_MemberData()
{
foreach (PfxInfo p in s_Certificates.Where(
c => c.IterationCount <= DefaultIterationLimit &&
string.IsNullOrEmpty(c.Password)))
{
yield return new object[] { p.Name, p.UsesPbes2, p.Blob, p.IterationCount };
}
}
public static IEnumerable<object[]> GetCertsWith_IterationCountExceedingDefaultLimit_MemberData()
{
foreach (PfxInfo p in s_Certificates.Where(c => c.IterationCount > DefaultIterationLimit))
{
yield return new object[] { p.Name, p.Password, p.UsesPbes2, p.Blob, p.IterationCount };
}
}
public static IEnumerable<object[]> GetCertsWith_NonNullOrEmptyPassword_MemberData()
{
foreach(PfxInfo p in s_Certificates.Where(c => !string.IsNullOrEmpty(c.Password)))
{
yield return new object[] { p.Name, p.Password, p.UsesPbes2, p.Blob, p.IterationCount };
}
}
}
public class PfxInfo
{
internal string Name { get; set; }
internal string? Password { get; set; }
internal long IterationCount { get; set; }
internal bool UsesPbes2 { get; set; }
internal byte[] Blob { get; set; }
internal PfxInfo(string name, string? password, long iterationCount, bool usesPbes2, byte[] blob)
{
Name = name;
Password = password;
IterationCount = iterationCount;
UsesPbes2 = usesPbes2;
Blob = blob;
}
}
}
// Licensed to the .NET Foundation under one or more agreements. // Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license. // The .NET Foundation licenses this file to you under the MIT license.
using Microsoft.DotNet.XUnitExtensions;
using System.Collections.Generic; using System.Collections.Generic;
using System.Reflection;
using System.Text;
using Test.Cryptography; using Test.Cryptography;
using Microsoft.DotNet.RemoteExecutor;
using Xunit; using Xunit;
using System.Linq;
namespace System.Security.Cryptography.X509Certificates.Tests namespace System.Security.Cryptography.X509Certificates.Tests
{ {
public static class PfxTests public static class PfxTests
{ {
private const long UnspecifiedIterations = -2;
private const long UnlimitedIterations = -1;
internal const long DefaultIterations = 600_000;
private const long DefaultIterationsWindows = 600_000;
// We don't know for sure this is a correct Windows version when this support was added but
// we know for a fact lower versions don't support it.
public static bool Pkcs12PBES2Supported => !PlatformDetection.IsWindows || PlatformDetection.IsWindows10Version1703OrGreater;
public static IEnumerable<object[]> BrainpoolCurvesPfx public static IEnumerable<object[]> BrainpoolCurvesPfx
{ {
get get
...@@ -448,6 +462,65 @@ public static void CollectionPerphemeralImport_HasKeyName() ...@@ -448,6 +462,65 @@ public static void CollectionPerphemeralImport_HasKeyName()
} }
} }
[ConditionalTheory]
[MemberData(memberName: nameof(PfxIterationCountTests.GetCertsWith_IterationCountNotExceedingDefaultLimit_AndNullOrEmptyPassword_MemberData), MemberType = typeof(PfxIterationCountTests))]
public static void TestIterationCounter(string name, bool usesPbes2, byte[] blob, int iterationCount)
{
_ = iterationCount;
MethodInfo method = typeof(X509Certificate).GetMethod("GetIterationCount", BindingFlags.Static | BindingFlags.NonPublic);
GetIterationCountDelegate target = method.CreateDelegate<GetIterationCountDelegate>();
if (usesPbes2 && !Pkcs12PBES2Supported)
{
throw new SkipTestException(name + " uses PBES2 which is not supported on this version.");
}
try
{
long count = (long)target(blob);
Assert.Equal(iterationCount, count);
}
catch (Exception e)
{
throw new Exception($"There's an error on certificate {name}, see inner exception for details", e);
}
}
internal static bool IsPkcs12IterationCountAllowed(long iterationCount, long allowedIterations)
{
if (allowedIterations == UnlimitedIterations)
{
return true;
}
if (allowedIterations == UnspecifiedIterations)
{
allowedIterations = DefaultIterations;
}
Assert.True(allowedIterations >= 0);
return iterationCount <= allowedIterations;
}
// This is a horrible way to produce SecureString. SecureString is deprecated and should not be used.
// This is only reasonable because it is a test driver.
internal static SecureString? GetSecureString(string password)
{
if (password == null)
return null;
SecureString secureString = new SecureString();
foreach (char c in password)
{
secureString.AppendChar(c);
}
return secureString;
}
// Keep the ECDsaCng-ness contained within this helper method so that it doesn't trigger a // Keep the ECDsaCng-ness contained within this helper method so that it doesn't trigger a
// FileNotFoundException on Unix. // FileNotFoundException on Unix.
private static void AssertEccAlgorithm(ECDsa ecdsa, string algorithmId) private static void AssertEccAlgorithm(ECDsa ecdsa, string algorithmId)
...@@ -476,5 +549,7 @@ private static X509Certificate2 Rewrap(this X509Certificate2 c) ...@@ -476,5 +549,7 @@ private static X509Certificate2 Rewrap(this X509Certificate2 c)
c.Dispose(); c.Dispose();
return newC; return newC;
} }
internal delegate ulong GetIterationCountDelegate(ReadOnlySpan<byte> pkcs12);
} }
} }
...@@ -53,6 +53,41 @@ public static void EmptyAiaResponseIsIgnored() ...@@ -53,6 +53,41 @@ public static void EmptyAiaResponseIsIgnored()
} }
} }
[Theory]
[InlineData(AiaResponseKind.Pkcs12, true)]
[InlineData(AiaResponseKind.Cert, false)]
public static void AiaAcceptsCertTypesAndIgnoresNonCertTypes(AiaResponseKind aiaResponseKind, bool mustIgnore)
{
CertificateAuthority.BuildPrivatePki(
PkiOptions.AllRevocation,
out RevocationResponder responder,
out CertificateAuthority root,
out CertificateAuthority intermediate,
out X509Certificate2 endEntity,
pkiOptionsInSubject: false,
testName: Guid.NewGuid().ToString());
using (responder)
using (root)
using (intermediate)
using (endEntity)
using (X509Certificate2 rootCert = root.CloneIssuerCert())
{
responder.AiaResponseKind = aiaResponseKind;
using (ChainHolder holder = new ChainHolder())
{
X509Chain chain = holder.Chain;
chain.ChainPolicy.CustomTrustStore.Add(rootCert);
chain.ChainPolicy.TrustMode = X509ChainTrustMode.CustomRootTrust;
chain.ChainPolicy.VerificationTime = endEntity.NotBefore.AddMinutes(1);
chain.ChainPolicy.UrlRetrievalTimeout = DynamicRevocationTests.s_urlRetrievalLimit;
Assert.NotEqual(mustIgnore, chain.Build(endEntity));
}
}
}
[Fact] [Fact]
[SkipOnPlatform(PlatformSupport.MobileAppleCrypto, "CA store is not available")] [SkipOnPlatform(PlatformSupport.MobileAppleCrypto, "CA store is not available")]
public static void DisableAiaOptionWorks() public static void DisableAiaOptionWorks()
......
...@@ -35,6 +35,11 @@ ...@@ -35,6 +35,11 @@
<Compile Include="PfxFormatTests.SingleCertGenerator.cs" /> <Compile Include="PfxFormatTests.SingleCertGenerator.cs" />
<Compile Include="PfxFormatTests_Collection.cs" /> <Compile Include="PfxFormatTests_Collection.cs" />
<Compile Include="PfxFormatTests_SingleCert.cs" /> <Compile Include="PfxFormatTests_SingleCert.cs" />
<Compile Include="PfxIterationCountTests.CustomAppDomainDataLimit.cs" />
<Compile Include="PfxIterationCountTests.X509Certificate2Collection.cs" />
<Compile Include="PfxIterationCountTests.X509Certificate2.cs" />
<Compile Include="PfxIterationCountTests.X509Certificate.cs" />
<Compile Include="PfxIterationCountTests.cs" />
<Compile Include="PfxTests.cs" /> <Compile Include="PfxTests.cs" />
<Compile Include="PropsTests.cs" /> <Compile Include="PropsTests.cs" />
<Compile Include="PublicKeyTests.cs" /> <Compile Include="PublicKeyTests.cs" />
......
...@@ -20,6 +20,13 @@ public TempFileHolder(ReadOnlySpan<char> content) ...@@ -20,6 +20,13 @@ public TempFileHolder(ReadOnlySpan<char> content)
} }
} }
public TempFileHolder(byte[] content)
{
FilePath = Path.GetTempFileName();
File.WriteAllBytes(FilePath, content);
}
public void Dispose() public void Dispose()
{ {
try try
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册