Skip to content

Commit 4352c7a

Browse files
authored
Attempt to load X.509 keys as ECDH keys first
Windows CNG EC keys always have a key usage attached to them. ECDH keys can be treated as either ECDH or ECDSA. However, a CNG key with ECDSA usage can only be used as ECDSA. When we import a PEM aggregate with an ECC private key, we should attempt to import it as ECDH first, if the certificate's key usage permits it. This will allow the imported key to act as either ECDH or ECDSA. However, if we attempt to import as ECDSA first, it will succeed however not have the correct key usage, preventing it from being used as ECDH.
1 parent 0c6ac9c commit 4352c7a

File tree

2 files changed

+60
-12
lines changed

2 files changed

+60
-12
lines changed

src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/X509Certificate2.cs

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1264,18 +1264,18 @@ public static X509Certificate2 CreateFromPem(ReadOnlySpan<char> certPem, ReadOnl
12641264
s_DsaPublicKeyPrivateKeyLabels,
12651265
static keyPem => CreateAndImport(keyPem, DSA.Create),
12661266
certificate.CopyWithPrivateKey),
1267-
Oids.EcPublicKey when IsECDsa(certificate) =>
1268-
ExtractKeyFromPem<ECDsa>(
1269-
keyPem,
1270-
s_EcPublicKeyPrivateKeyLabels,
1271-
static keyPem => CreateAndImport(keyPem, ECDsa.Create),
1272-
certificate.CopyWithPrivateKey),
12731267
Oids.EcPublicKey when IsECDiffieHellman(certificate) =>
12741268
ExtractKeyFromPem<ECDiffieHellman>(
12751269
keyPem,
12761270
s_EcPublicKeyPrivateKeyLabels,
12771271
static keyPem => CreateAndImport(keyPem, ECDiffieHellman.Create),
12781272
certificate.CopyWithPrivateKey),
1273+
Oids.EcPublicKey when IsECDsa(certificate) =>
1274+
ExtractKeyFromPem<ECDsa>(
1275+
keyPem,
1276+
s_EcPublicKeyPrivateKeyLabels,
1277+
static keyPem => CreateAndImport(keyPem, ECDsa.Create),
1278+
certificate.CopyWithPrivateKey),
12791279
Oids.MlKem512 or Oids.MlKem768 or Oids.MlKem1024 =>
12801280
ExtractKeyFromPem<MLKem>(
12811281
keyPem,
@@ -1357,18 +1357,18 @@ public static X509Certificate2 CreateFromEncryptedPem(ReadOnlySpan<char> certPem
13571357
password,
13581358
static (keyPem, password) => CreateAndImportEncrypted(keyPem, password, DSA.Create),
13591359
certificate.CopyWithPrivateKey),
1360-
Oids.EcPublicKey when IsECDsa(certificate) =>
1361-
ExtractKeyFromEncryptedPem<ECDsa>(
1362-
keyPem,
1363-
password,
1364-
static (keyPem, password) => CreateAndImportEncrypted(keyPem, password, ECDsa.Create),
1365-
certificate.CopyWithPrivateKey),
13661360
Oids.EcPublicKey when IsECDiffieHellman(certificate) =>
13671361
ExtractKeyFromEncryptedPem<ECDiffieHellman>(
13681362
keyPem,
13691363
password,
13701364
static (keyPem, password) => CreateAndImportEncrypted(keyPem, password, ECDiffieHellman.Create),
13711365
certificate.CopyWithPrivateKey),
1366+
Oids.EcPublicKey when IsECDsa(certificate) =>
1367+
ExtractKeyFromEncryptedPem<ECDsa>(
1368+
keyPem,
1369+
password,
1370+
static (keyPem, password) => CreateAndImportEncrypted(keyPem, password, ECDsa.Create),
1371+
certificate.CopyWithPrivateKey),
13721372
Oids.MlKem512 or Oids.MlKem768 or Oids.MlKem1024 =>
13731373
ExtractKeyFromEncryptedPem<MLKem>(
13741374
keyPem,

src/libraries/System.Security.Cryptography/tests/X509Certificates/X509Certificate2PemTests.cs

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,54 @@ public static void CreateFromPem_ECDH_Pkcs8_Success()
269269
}
270270
}
271271

272+
[Fact]
273+
public static void CreateFromPem_EC_Pkcs8_Success()
274+
{
275+
// ecPublicKey certificates that have no key usage restrictions should be allowed to be used as both
276+
// an ECDsa key and an ECDiffieHellman key.
277+
278+
// For purposes of creating the certificate, it doesn't matter if we use an ECDSA or ECDH key, but starting
279+
// with ECDSA means we can make a self-signed cert.
280+
using ECDsa key = ECDsa.Create();
281+
key.ImportFromPem(TestData.EcDhPkcs8Key);
282+
CertificateRequest req = new("CN=radish", key, HashAlgorithmName.SHA256);
283+
using X509Certificate2 cert = req.CreateSelfSigned(DateTimeOffset.Now, DateTimeOffset.Now.AddYears(1));
284+
string pemAggregate = $"{cert.ExportCertificatePem()}\n{TestData.EcDhPkcs8Key}";
285+
using X509Certificate2 reLoaded = X509Certificate2.CreateFromPem(pemAggregate, pemAggregate);
286+
287+
AssertKeysMatch(TestData.EcDhPkcs8Key, reLoaded.GetECDiffieHellmanPrivateKey);
288+
AssertKeysMatch(TestData.EcDhPkcs8Key, reLoaded.GetECDsaPrivateKey);
289+
AssertExtensions.SequenceEqual(cert.SerialNumberBytes.Span, reLoaded.SerialNumberBytes.Span);
290+
}
291+
292+
[Fact]
293+
public static void CreateFromEncryptedPem_EC_Pkcs8_Success()
294+
{
295+
// ecPublicKey certificates that have no key usage restrictions should be allowed to be used as both
296+
// an ECDsa key and an ECDiffieHellman key.
297+
298+
// For purposes of creating the certificate, it doesn't matter if we use an ECDSA or ECDH key, but starting
299+
// with ECDSA means we can make a self-signed cert.
300+
using ECDsa key = ECDsa.Create();
301+
key.ImportFromPem(TestData.EcDhPkcs8Key);
302+
CertificateRequest req = new("CN=radish", key, HashAlgorithmName.SHA256);
303+
using X509Certificate2 cert = req.CreateSelfSigned(DateTimeOffset.Now, DateTimeOffset.Now.AddYears(1));
304+
305+
PbeParameters pbe = new(PbeEncryptionAlgorithm.Aes128Cbc, HashAlgorithmName.SHA1, 32);
306+
const string Password = "PLACEHOLDER";
307+
308+
string encryptedPrivateKey = PemEncoding.WriteString(
309+
"ENCRYPTED PRIVATE KEY",
310+
key.ExportEncryptedPkcs8PrivateKey(Password, pbe));
311+
312+
string pemAggregate = $"{cert.ExportCertificatePem()}\n{encryptedPrivateKey}";
313+
using X509Certificate2 reLoaded = X509Certificate2.CreateFromEncryptedPem(pemAggregate, pemAggregate, Password);
314+
315+
AssertKeysMatch(encryptedPrivateKey, reLoaded.GetECDiffieHellmanPrivateKey, Password);
316+
AssertKeysMatch(encryptedPrivateKey, reLoaded.GetECDsaPrivateKey, Password);
317+
AssertExtensions.SequenceEqual(cert.SerialNumberBytes.Span, reLoaded.SerialNumberBytes.Span);
318+
}
319+
272320
[ConditionalFact(typeof(MLKem), nameof(MLKem.IsSupported))]
273321
public static void CreateFromPem_MLKem_Pkcs8_Success()
274322
{

0 commit comments

Comments
 (0)