Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement ExportPkcs12 #112569

Merged
merged 19 commits into from
Feb 20, 2025
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Collection tests
  • Loading branch information
vcsjones committed Feb 14, 2025
commit 7054cf5309e04867af7ad4d046d623d3695c05a4
Original file line number Diff line number Diff line change
Expand Up @@ -1611,6 +1611,201 @@ public static void ExportCertificatePems_MultiCert()
}
}

[Theory]
[InlineData(Pkcs12ExportPbeParameters.Pkcs12TripleDesSha1, nameof(HashAlgorithmName.SHA1), PbeEncryptionAlgorithm.TripleDes3KeyPkcs12)]
[InlineData(Pkcs12ExportPbeParameters.Pbes2Aes256Sha256, nameof(HashAlgorithmName.SHA256), PbeEncryptionAlgorithm.Aes256Cbc)]
[SkipOnPlatform(TestPlatforms.iOS | TestPlatforms.MacCatalyst | TestPlatforms.tvOS, "The PKCS#12 Exportable flag is not supported on iOS/MacCatalyst/tvOS")]
public static void ExportPkcs12_OneCert(
Pkcs12ExportPbeParameters pkcs12ExportPbeParameters,
string expectedHashAlgorithm,
PbeEncryptionAlgorithm expectedEncryptionAlgorithm)
{
const string password = "PLACEHOLDER";
using X509Certificate2 cert = new(TestData.PfxData, TestData.PfxDataPassword, X509KeyStorageFlags.Exportable);
X509Certificate2Collection collection = [cert];

byte[] pkcs12 = collection.ExportPkcs12(pkcs12ExportPbeParameters, password);
(int certs, int keys) = ExportTests.VerifyPkcs12(
pkcs12,
password,
expectedIterations: 2000,
expectedMacHashAlgorithm: new HashAlgorithmName(expectedHashAlgorithm),
expectedEncryptionAlgorithm);
Assert.Equal(1, certs);
Assert.Equal(1, keys);
}

[Theory]
[SkipOnPlatform(TestPlatforms.iOS | TestPlatforms.MacCatalyst | TestPlatforms.tvOS, "The PKCS#12 Exportable flag is not supported on iOS/MacCatalyst/tvOS")]
[InlineData(PbeEncryptionAlgorithm.Aes192Cbc, nameof(HashAlgorithmName.SHA1), 1200)]
[InlineData(PbeEncryptionAlgorithm.Aes256Cbc, nameof(HashAlgorithmName.SHA256), 4000)]
[InlineData(PbeEncryptionAlgorithm.Aes128Cbc, nameof(HashAlgorithmName.SHA256), 4)]
[InlineData(PbeEncryptionAlgorithm.TripleDes3KeyPkcs12, nameof(HashAlgorithmName.SHA1), 1234)]
public static void ExportPkcs12_PbeParameters_OneCert(
PbeEncryptionAlgorithm encryptionAlgorithm,
string hashAlgorithm,
int iterations)
{
const string password = "PLACEHOLDER";
HashAlgorithmName hashAlgorithmName = new(hashAlgorithm);
PbeParameters parameters = new(encryptionAlgorithm, hashAlgorithmName, iterations);

using X509Certificate2 cert = new(TestData.PfxData, TestData.PfxDataPassword, X509KeyStorageFlags.Exportable);
X509Certificate2Collection collection = [cert];

byte[] pkcs12 = collection.ExportPkcs12(parameters, password);
(int certs, int keys) = ExportTests.VerifyPkcs12(
pkcs12,
password,
expectedIterations: iterations,
expectedMacHashAlgorithm: hashAlgorithmName,
encryptionAlgorithm);
Assert.Equal(1, certs);
Assert.Equal(1, keys);
}

[Theory]
[SkipOnPlatform(TestPlatforms.iOS | TestPlatforms.MacCatalyst | TestPlatforms.tvOS, "The PKCS#12 Exportable flag is not supported on iOS/MacCatalyst/tvOS")]
[InlineData(PbeEncryptionAlgorithm.Aes192Cbc, nameof(HashAlgorithmName.SHA1), 1200)]
[InlineData(PbeEncryptionAlgorithm.Aes256Cbc, nameof(HashAlgorithmName.SHA256), 4000)]
[InlineData(PbeEncryptionAlgorithm.Aes128Cbc, nameof(HashAlgorithmName.SHA256), 4)]
[InlineData(PbeEncryptionAlgorithm.TripleDes3KeyPkcs12, nameof(HashAlgorithmName.SHA1), 1234)]
public static void ExportPkcs12_PbeParameters_TwoCerts(
PbeEncryptionAlgorithm encryptionAlgorithm,
string hashAlgorithm,
int iterations)
{
const string password = "PLACEHOLDER";
HashAlgorithmName hashAlgorithmName = new(hashAlgorithm);
PbeParameters parameters = new(encryptionAlgorithm, hashAlgorithmName, iterations);

using X509Certificate2 cert1 = new(TestData.PfxData, TestData.PfxDataPassword, X509KeyStorageFlags.Exportable);
using X509Certificate2 cert2 = new(TestData.Pkcs12Builder3DESCBCWithEmptyPassword, (string)null, X509KeyStorageFlags.Exportable);
X509Certificate2Collection collection = [cert1, cert2];

byte[] pkcs12 = collection.ExportPkcs12(parameters, password);
(int certs, int keys) = ExportTests.VerifyPkcs12(
pkcs12,
password,
iterations,
hashAlgorithmName,
encryptionAlgorithm);
Assert.Equal(2, certs);
Assert.Equal(2, keys);
}

[Theory]
[SkipOnPlatform(TestPlatforms.iOS | TestPlatforms.MacCatalyst | TestPlatforms.tvOS, "The PKCS#12 Exportable flag is not supported on iOS/MacCatalyst/tvOS")]
[InlineData(Pkcs12ExportPbeParameters.Pkcs12TripleDesSha1, nameof(HashAlgorithmName.SHA1), PbeEncryptionAlgorithm.TripleDes3KeyPkcs12)]
[InlineData(Pkcs12ExportPbeParameters.Pbes2Aes256Sha256, nameof(HashAlgorithmName.SHA256), PbeEncryptionAlgorithm.Aes256Cbc)]
public static void ExportPkcs12_TwoCerts(
Pkcs12ExportPbeParameters pkcs12ExportPbeParameters,
string expectedHashAlgorithm,
PbeEncryptionAlgorithm expectedEncryptionAlgorithm)
{
const string password = "PLACEHOLDER";

using X509Certificate2 cert1 = new(TestData.PfxData, TestData.PfxDataPassword, X509KeyStorageFlags.Exportable);
using X509Certificate2 cert2 = new(TestData.Pkcs12Builder3DESCBCWithEmptyPassword, (string)null, X509KeyStorageFlags.Exportable);
X509Certificate2Collection collection = [cert1, cert2];

byte[] pkcs12 = collection.ExportPkcs12(pkcs12ExportPbeParameters, password);
(int certs, int keys) = ExportTests.VerifyPkcs12(
pkcs12,
password,
expectedIterations: 2000,
new HashAlgorithmName(expectedHashAlgorithm),
expectedEncryptionAlgorithm);
Assert.Equal(2, certs);
Assert.Equal(2, keys);
}

[Theory]
[InlineData(PbeEncryptionAlgorithm.Aes192Cbc, nameof(HashAlgorithmName.SHA1), 1200)]
[InlineData(PbeEncryptionAlgorithm.Aes256Cbc, nameof(HashAlgorithmName.SHA256), 4000)]
[InlineData(PbeEncryptionAlgorithm.Aes128Cbc, nameof(HashAlgorithmName.SHA256), 4)]
[InlineData(PbeEncryptionAlgorithm.TripleDes3KeyPkcs12, nameof(HashAlgorithmName.SHA1), 1234)]
public static void ExportPkcs12_PbeParameters_CertOnly(
PbeEncryptionAlgorithm encryptionAlgorithm,
string hashAlgorithm,
int iterations)
{
const string password = "PLACEHOLDER";
HashAlgorithmName hashAlgorithmName = new(hashAlgorithm);
PbeParameters parameters = new(encryptionAlgorithm, hashAlgorithmName, iterations);

using X509Certificate2 cert1 = new(TestData.MsCertificate);
using X509Certificate2 cert2 = new(TestData.MicrosoftDotComRootBytes);
X509Certificate2Collection collection = [cert1, cert2];

byte[] pkcs12 = collection.ExportPkcs12(parameters, password);
(int certs, int keys) = ExportTests.VerifyPkcs12(
pkcs12,
password,
iterations,
hashAlgorithmName,
encryptionAlgorithm);
Assert.Equal(2, certs);
Assert.Equal(0, keys);
}

[Theory]
[InlineData(Pkcs12ExportPbeParameters.Pkcs12TripleDesSha1, nameof(HashAlgorithmName.SHA1), PbeEncryptionAlgorithm.TripleDes3KeyPkcs12)]
[InlineData(Pkcs12ExportPbeParameters.Pbes2Aes256Sha256, nameof(HashAlgorithmName.SHA256), PbeEncryptionAlgorithm.Aes256Cbc)]
public static void ExportPkcs12_CertsOnly(
Pkcs12ExportPbeParameters pkcs12ExportPbeParameters,
string expectedHashAlgorithm,
PbeEncryptionAlgorithm expectedEncryptionAlgorithm)
{
const string password = "PLACEHOLDER";
using X509Certificate2 cert1 = new(TestData.MsCertificate);
using X509Certificate2 cert2 = new(TestData.MicrosoftDotComRootBytes);
X509Certificate2Collection collection = [cert1, cert2];

byte[] pkcs12 = collection.ExportPkcs12(pkcs12ExportPbeParameters, password);
(int certs, int keys) = ExportTests.VerifyPkcs12(
pkcs12,
password,
expectedIterations: 2000,
new HashAlgorithmName(expectedHashAlgorithm),
expectedEncryptionAlgorithm);
Assert.Equal(2, certs);
Assert.Equal(0, keys);
}

[Fact]
public static void ExportPkcs12_Pkcs12ExportPbeParameters_ArgValidation()
{
X509Certificate2Collection collection = [];
AssertExtensions.Throws<ArgumentOutOfRangeException>("exportParameters",
() => collection.ExportPkcs12((Pkcs12ExportPbeParameters)42, null));
}

[Theory]
[InlineData(PbeEncryptionAlgorithm.TripleDes3KeyPkcs12, nameof(HashAlgorithmName.SHA256))]
[InlineData(PbeEncryptionAlgorithm.TripleDes3KeyPkcs12, "")]
[InlineData(PbeEncryptionAlgorithm.TripleDes3KeyPkcs12, null)]
[InlineData(PbeEncryptionAlgorithm.TripleDes3KeyPkcs12, "POTATO")]
[InlineData(PbeEncryptionAlgorithm.Aes128Cbc, "POTATO")]
[InlineData(PbeEncryptionAlgorithm.Aes128Cbc, null)]
[InlineData(PbeEncryptionAlgorithm.Aes128Cbc, "")]
[InlineData(PbeEncryptionAlgorithm.Aes192Cbc, "POTATO")]
[InlineData(PbeEncryptionAlgorithm.Aes192Cbc, null)]
[InlineData(PbeEncryptionAlgorithm.Aes192Cbc, "")]
[InlineData(PbeEncryptionAlgorithm.Aes256Cbc, "POTATO")]
[InlineData(PbeEncryptionAlgorithm.Aes256Cbc, null)]
[InlineData(PbeEncryptionAlgorithm.Aes256Cbc, "")]
[InlineData(PbeEncryptionAlgorithm.Aes256Cbc, "SHA3-256")]
[InlineData((PbeEncryptionAlgorithm)(-1), nameof(HashAlgorithmName.SHA1))]
public static void ExportPkcs12_PbeParameters_ArgValidation(
PbeEncryptionAlgorithm encryptionAlgorithm,
string hashAlgorithm)
{
X509Certificate2Collection collection = [];
PbeParameters badParameters = new(encryptionAlgorithm, new HashAlgorithmName(hashAlgorithm), 1);
Assert.Throws<CryptographicException>(() => collection.ExportPkcs12(badParameters, null));
}

private static void TestExportSingleCert_SecureStringPassword(X509ContentType ct)
{
using (var pfxCer = new X509Certificate2(TestData.PfxData, TestData.CreatePfxDataPasswordSecureString(), Cert.EphemeralIfPossible))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -148,12 +148,14 @@ public static void ExportPkcs12(
using (X509Certificate2 cert = new(TestData.PfxData, TestData.PfxDataPassword, X509KeyStorageFlags.Exportable))
{
byte[] pkcs12 = cert.ExportPkcs12(pkcs12ExportPbeParameters, password);
VerifyPkcs12(
(int certs, int keys) = VerifyPkcs12(
pkcs12,
password,
expectedIterations: 2000,
expectedMacHashAlgorithm: new HashAlgorithmName(expectedHashAlgorithm),
expectedEncryptionAlgorithm);
Assert.Equal(1, certs);
Assert.Equal(1, keys);
}
}

Expand All @@ -170,18 +172,71 @@ public static void ExportPkcs12_PbeParameters(
{
const string password = "PLACEHOLDER";
HashAlgorithmName hashAlgorithmName = new(hashAlgorithm);

PbeParameters parameters = new(encryptionAlgorithm, hashAlgorithmName, iterations);

using (X509Certificate2 cert = new(TestData.PfxData, TestData.PfxDataPassword, X509KeyStorageFlags.Exportable))
{
byte[] pkcs12 = cert.ExportPkcs12(parameters, password);
VerifyPkcs12(
(int certs, int keys) = VerifyPkcs12(
pkcs12,
password,
iterations,
hashAlgorithmName,
encryptionAlgorithm);
Assert.Equal(1, certs);
Assert.Equal(1, keys);
}
}

[Theory]
[InlineData(PbeEncryptionAlgorithm.Aes192Cbc, nameof(HashAlgorithmName.SHA1), 1200)]
[InlineData(PbeEncryptionAlgorithm.Aes256Cbc, nameof(HashAlgorithmName.SHA256), 4000)]
[InlineData(PbeEncryptionAlgorithm.Aes128Cbc, nameof(HashAlgorithmName.SHA256), 4)]
[InlineData(PbeEncryptionAlgorithm.TripleDes3KeyPkcs12, nameof(HashAlgorithmName.SHA1), 1234)]
public static void ExportPkcs12_PbeParameters_CertOnly(
PbeEncryptionAlgorithm encryptionAlgorithm,
string hashAlgorithm,
int iterations)
{
const string password = "PLACEHOLDER";
HashAlgorithmName hashAlgorithmName = new(hashAlgorithm);
PbeParameters parameters = new(encryptionAlgorithm, hashAlgorithmName, iterations);

using (X509Certificate2 cert = new(TestData.MsCertificate))
{
byte[] pkcs12 = cert.ExportPkcs12(parameters, password);
(int certs, int keys) = VerifyPkcs12(
pkcs12,
password,
iterations,
hashAlgorithmName,
encryptionAlgorithm);
Assert.Equal(1, certs);
Assert.Equal(0, keys);
}
}

[Theory]
[InlineData(Pkcs12ExportPbeParameters.Pkcs12TripleDesSha1, nameof(HashAlgorithmName.SHA1), PbeEncryptionAlgorithm.TripleDes3KeyPkcs12)]
[InlineData(Pkcs12ExportPbeParameters.Pbes2Aes256Sha256, nameof(HashAlgorithmName.SHA256), PbeEncryptionAlgorithm.Aes256Cbc)]
public static void ExportPkcs12_CertOnly(
Pkcs12ExportPbeParameters pkcs12ExportPbeParameters,
string expectedHashAlgorithm,
PbeEncryptionAlgorithm expectedEncryptionAlgorithm)
{
const string password = "PLACEHOLDER";

using (X509Certificate2 cert = new(TestData.MsCertificate))
{
byte[] pkcs12 = cert.ExportPkcs12(pkcs12ExportPbeParameters, password);
(int certs, int keys) = VerifyPkcs12(
pkcs12,
password,
expectedIterations: 2000,
expectedMacHashAlgorithm: new HashAlgorithmName(expectedHashAlgorithm),
expectedEncryptionAlgorithm);
Assert.Equal(1, certs);
Assert.Equal(0, keys);
}
}

Expand Down Expand Up @@ -698,7 +753,7 @@ private static (byte[] Pkcs12, TKey key) CreateSimplePkcs12<TKey>() where TKey :
}
}

private static void VerifyPkcs12(
internal static (int certs, int keys) VerifyPkcs12(
byte[] pkcs12,
string password,
int expectedIterations,
Expand Down Expand Up @@ -728,6 +783,9 @@ private static void VerifyPkcs12(
AsnValueReader sequenceReader = authSafeReader.ReadSequence();
authSafeReader.ThrowIfNotEmpty();

int certs = 0;
int keys = 0;

while (sequenceReader.HasData)
{
ContentInfoAsn.Decode(ref sequenceReader, safeContents, out ContentInfoAsn contentInfo);
Expand All @@ -742,8 +800,11 @@ private static void VerifyPkcs12(

foreach (Pkcs12SafeContents pkcs12SafeContents in info.AuthenticatedSafe)
{
bool wasEncryptedSafe = false;

if (pkcs12SafeContents.ConfidentialityMode == Pkcs12ConfidentialityMode.Password)
{
wasEncryptedSafe = true;
pkcs12SafeContents.Decrypt(password);
}

Expand All @@ -755,10 +816,17 @@ private static void VerifyPkcs12(
shroudedKeyBag.EncryptedPkcs8PrivateKey,
AsnEncodingRules.BER);
AssertEncryptionAlgorithm(epki.EncryptionAlgorithm);
keys++;
}
else if (safeBag is Pkcs12CertBag && wasEncryptedSafe)
{
certs++;
}
}
}

return (certs, keys);

void AssertEncryptionAlgorithm(AlgorithmIdentifierAsn algorithmIdentifier)
{
if (expectedEncryptionAlgorithm == PbeEncryptionAlgorithm.TripleDes3KeyPkcs12)
Expand Down