99using System . Net ;
1010using System . Runtime . Serialization ;
1111using System . Runtime . Versioning ;
12+ using System . Security . Cryptography . Asn1 ;
1213using System . Security . Cryptography . X509Certificates . Asn1 ;
1314using System . Text ;
1415using Internal . Cryptography ;
@@ -24,9 +25,8 @@ public class X509Certificate2 : X509Certificate
2425 private volatile PublicKey ? _lazyPublicKey ;
2526 private volatile AsymmetricAlgorithm ? _lazyPrivateKey ;
2627 private volatile X509ExtensionCollection ? _lazyExtensions ;
27- private static readonly string [ ] s_EcPublicKeyPrivateKeyLabels = { PemLabels . EcPrivateKey , PemLabels . Pkcs8PrivateKey } ;
28- private static readonly string [ ] s_RsaPublicKeyPrivateKeyLabels = { PemLabels . RsaPrivateKey , PemLabels . Pkcs8PrivateKey } ;
29- private static readonly string [ ] s_DsaPublicKeyPrivateKeyLabels = { PemLabels . Pkcs8PrivateKey } ;
28+ private static readonly string [ ] s_RsaPublicKeyPrivateKeyLabels = [ PemLabels . RsaPrivateKey , PemLabels . Pkcs8PrivateKey ] ;
29+ private static readonly string [ ] s_DsaPublicKeyPrivateKeyLabels = [ PemLabels . Pkcs8PrivateKey ] ;
3030
3131 public override void Reset ( )
3232 {
@@ -1378,18 +1378,7 @@ public static X509Certificate2 CreateFromPem(ReadOnlySpan<char> certPem, ReadOnl
13781378 s_DsaPublicKeyPrivateKeyLabels ,
13791379 static keyPem => CreateAndImport ( keyPem , DSA . Create ) ,
13801380 certificate . CopyWithPrivateKey ) ,
1381- Oids . EcPublicKey when IsECDiffieHellman ( certificate ) =>
1382- ExtractKeyFromPem < ECDiffieHellman > (
1383- keyPem ,
1384- s_EcPublicKeyPrivateKeyLabels ,
1385- static keyPem => CreateAndImport ( keyPem , ECDiffieHellman . Create ) ,
1386- certificate . CopyWithPrivateKey ) ,
1387- Oids . EcPublicKey when IsECDsa ( certificate ) =>
1388- ExtractKeyFromPem < ECDsa > (
1389- keyPem ,
1390- s_EcPublicKeyPrivateKeyLabels ,
1391- static keyPem => CreateAndImport ( keyPem , ECDsa . Create ) ,
1392- certificate . CopyWithPrivateKey ) ,
1381+ Oids . EcPublicKey => ExtractKeyFromECPem ( certificate , keyPem ) ,
13931382 Oids . MlKem512 or Oids . MlKem768 or Oids . MlKem1024 =>
13941383 ExtractKeyFromPem < MLKem > (
13951384 keyPem ,
@@ -1477,18 +1466,7 @@ public static X509Certificate2 CreateFromEncryptedPem(ReadOnlySpan<char> certPem
14771466 password ,
14781467 static ( keyPem , password ) => CreateAndImportEncrypted ( keyPem , password , DSA . Create ) ,
14791468 certificate . CopyWithPrivateKey ) ,
1480- Oids . EcPublicKey when IsECDiffieHellman ( certificate ) =>
1481- ExtractKeyFromEncryptedPem < ECDiffieHellman > (
1482- keyPem ,
1483- password ,
1484- static ( keyPem , password ) => CreateAndImportEncrypted ( keyPem , password , ECDiffieHellman . Create ) ,
1485- certificate . CopyWithPrivateKey ) ,
1486- Oids . EcPublicKey when IsECDsa ( certificate ) =>
1487- ExtractKeyFromEncryptedPem < ECDsa > (
1488- keyPem ,
1489- password ,
1490- static ( keyPem , password ) => CreateAndImportEncrypted ( keyPem , password , ECDsa . Create ) ,
1491- certificate . CopyWithPrivateKey ) ,
1469+ Oids . EcPublicKey => ExtractKeyFromEncryptedECPem ( certificate , keyPem , password ) ,
14921470 Oids . MlKem512 or Oids . MlKem768 or Oids . MlKem1024 =>
14931471 ExtractKeyFromEncryptedPem < MLKem > (
14941472 keyPem ,
@@ -1929,24 +1907,32 @@ private static X509Certificate2 ExtractKeyFromPem<TAlg>(
19291907 {
19301908 if ( label . SequenceEqual ( eligibleLabel ) )
19311909 {
1932- using ( TAlg key = factory ( contents [ fields . Location ] ) )
1933- {
1934- try
1935- {
1936- return import ( key ) ;
1937- }
1938- catch ( ArgumentException ae )
1939- {
1940- throw new CryptographicException ( SR . Cryptography_X509_NoOrMismatchedPemKey , ae ) ;
1941- }
1942- }
1910+ return ExtractKeyFromPem ( contents [ fields . Location ] , factory , import ) ;
19431911 }
19441912 }
19451913 }
19461914
19471915 throw new CryptographicException ( SR . Cryptography_X509_NoOrMismatchedPemKey ) ;
19481916 }
19491917
1918+ private static X509Certificate2 ExtractKeyFromPem < TAlg > (
1919+ ReadOnlySpan < char > keyPem ,
1920+ Func < ReadOnlySpan < char > , TAlg > factory ,
1921+ Func < TAlg , X509Certificate2 > import ) where TAlg : IDisposable
1922+ {
1923+ using ( TAlg key = factory ( keyPem ) )
1924+ {
1925+ try
1926+ {
1927+ return import ( key ) ;
1928+ }
1929+ catch ( ArgumentException ae )
1930+ {
1931+ throw new CryptographicException ( SR . Cryptography_X509_NoOrMismatchedPemKey , ae ) ;
1932+ }
1933+ }
1934+ }
1935+
19501936 private static X509Certificate2 ExtractKeyFromEncryptedPem < TAlg > (
19511937 ReadOnlySpan < char > keyPem ,
19521938 ReadOnlySpan < char > password ,
@@ -2009,5 +1995,238 @@ private static bool HasECDiffieHellmanKeyUsage(X509Certificate2 certificate)
20091995 // considered valid for all usages, so we can use it for ECDH.
20101996 return true ;
20111997 }
1998+
1999+ [ UnsupportedOSPlatform ( "browser" ) ]
2000+ private static X509Certificate2 ExtractKeyFromEncryptedECPem (
2001+ X509Certificate2 certificate ,
2002+ ReadOnlySpan < char > keyPem ,
2003+ ReadOnlySpan < char > password )
2004+ {
2005+ Debug . Assert ( certificate . GetKeyAlgorithm ( ) == Oids . EcPublicKey ) ;
2006+
2007+ foreach ( ( ReadOnlySpan < char > contents , PemFields fields ) in PemEnumerator . Utf16 ( keyPem ) )
2008+ {
2009+ ReadOnlySpan < char > label = contents [ fields . Label ] ;
2010+
2011+ if ( ! label . SequenceEqual ( PemLabels . EncryptedPkcs8PrivateKey ) )
2012+ {
2013+ continue ;
2014+ }
2015+
2016+ byte [ ] base64Buffer = CryptoPool . Rent ( fields . DecodedDataLength ) ;
2017+ int base64ClearSize = CryptoPool . ClearAll ;
2018+ ArraySegment < byte > ? decryptedPkcs8 = null ;
2019+
2020+ try
2021+ {
2022+ bool result = Convert . TryFromBase64Chars ( contents [ fields . Base64Data ] , base64Buffer , out int base64Written ) ;
2023+
2024+ if ( ! result || base64Written != fields . DecodedDataLength )
2025+ {
2026+ Debug . Fail ( "Preallocated buffer and validated data decoding failed." ) ;
2027+ break ;
2028+ }
2029+
2030+ base64ClearSize = base64Written ;
2031+ Debug . Assert ( ! decryptedPkcs8 . HasValue ) ;
2032+ decryptedPkcs8 = KeyFormatHelper . DecryptPkcs8 ( password , base64Buffer . AsMemory ( 0 , base64Written ) , out int bytesRead ) ;
2033+
2034+ if ( bytesRead != base64Written )
2035+ {
2036+ break ;
2037+ }
2038+
2039+ X509Certificate2 ? loaded = ExtractKeyFromECPrivateKeyInfo ( certificate , decryptedPkcs8 . Value ) ;
2040+
2041+ if ( loaded is null )
2042+ {
2043+ break ;
2044+ }
2045+
2046+ return loaded ;
2047+ }
2048+ catch ( CryptographicException ce )
2049+ {
2050+ throw new CryptographicException ( SR . Cryptography_X509_NoOrMismatchedPemKey , ce ) ;
2051+ }
2052+ finally
2053+ {
2054+ CryptoPool . Return ( base64Buffer , base64ClearSize ) ;
2055+
2056+ if ( decryptedPkcs8 . HasValue )
2057+ {
2058+ CryptoPool . Return ( decryptedPkcs8 . Value ) ;
2059+ }
2060+ }
2061+ }
2062+
2063+ throw new CryptographicException ( SR . Cryptography_X509_NoOrMismatchedPemKey ) ;
2064+ }
2065+
2066+ [ UnsupportedOSPlatform ( "browser" ) ]
2067+ private static X509Certificate2 ExtractKeyFromECPem ( X509Certificate2 certificate , ReadOnlySpan < char > keyPem )
2068+ {
2069+ Debug . Assert ( certificate . GetKeyAlgorithm ( ) == Oids . EcPublicKey ) ;
2070+
2071+ foreach ( ( ReadOnlySpan < char > contents , PemFields fields ) in PemEnumerator . Utf16 ( keyPem ) )
2072+ {
2073+ ReadOnlySpan < char > label = contents [ fields . Label ] ;
2074+
2075+ if ( label . SequenceEqual ( PemLabels . EcPrivateKey ) )
2076+ {
2077+ // EC PRIVATE KEYs do not have a key usage, so usage is determined by the certificate.
2078+
2079+ // If we can load it is EC-DH, we should prefer that over EC-DSA. ECC keys that are "both" prefer
2080+ // to be imported as EC-DH. Importing it as EC-DSA would restrict it to EC-DSA, even if the key
2081+ // and certificate are valid for EC-DH. Other platforms don't have such restrictions.
2082+ if ( IsECDiffieHellman ( certificate ) )
2083+ {
2084+ return ExtractKeyFromPem (
2085+ keyPem ,
2086+ static keyPem => CreateAndImport ( keyPem , ECDiffieHellman . Create ) ,
2087+ certificate . CopyWithPrivateKey ) ;
2088+ }
2089+
2090+ if ( IsECDsa ( certificate ) )
2091+ {
2092+ return ExtractKeyFromPem (
2093+ keyPem ,
2094+ static keyPem => CreateAndImport ( keyPem , ECDsa . Create ) ,
2095+ certificate . CopyWithPrivateKey ) ;
2096+ }
2097+
2098+ // If we got here, then the key is neither EC-DH or EC-DSA eligible, but we had a matching PEM
2099+ // label. Break out and throw.
2100+ break ;
2101+ }
2102+
2103+ if ( ! label . SequenceEqual ( PemLabels . Pkcs8PrivateKey ) )
2104+ {
2105+ continue ;
2106+ }
2107+
2108+ byte [ ] base64Buffer = CryptoPool . Rent ( fields . DecodedDataLength ) ;
2109+ int clearSize = CryptoPool . ClearAll ;
2110+
2111+ try
2112+ {
2113+ bool result = Convert . TryFromBase64Chars ( contents [ fields . Base64Data ] , base64Buffer , out int base64Written ) ;
2114+
2115+ if ( ! result || base64Written != fields . DecodedDataLength )
2116+ {
2117+ Debug . Fail ( "Preallocated buffer and validated data decoding failed." ) ;
2118+ break ;
2119+ }
2120+
2121+ clearSize = base64Written ;
2122+ X509Certificate2 ? loaded = ExtractKeyFromECPrivateKeyInfo ( certificate , base64Buffer . AsMemory ( 0 , base64Written ) ) ;
2123+
2124+ if ( loaded is null )
2125+ {
2126+ break ;
2127+ }
2128+
2129+ return loaded ;
2130+ }
2131+ catch ( CryptographicException ce )
2132+ {
2133+ throw new CryptographicException ( SR . Cryptography_X509_NoOrMismatchedPemKey , ce ) ;
2134+ }
2135+ finally
2136+ {
2137+ CryptoPool . Return ( base64Buffer , clearSize ) ;
2138+ }
2139+ }
2140+
2141+ throw new CryptographicException ( SR . Cryptography_X509_NoOrMismatchedPemKey ) ;
2142+ }
2143+
2144+ [ UnsupportedOSPlatform ( "browser" ) ]
2145+ private static X509Certificate2 ? ExtractKeyFromECPrivateKeyInfo (
2146+ X509Certificate2 certificate ,
2147+ ReadOnlyMemory < byte > privateKeyInfo )
2148+ {
2149+ // We are not going to perform any validation on the PrivateKeyInfo here. We just need a hint
2150+ // what algorithm to use. The actual algorithm will do whatever validation on the key as needed.
2151+ PrivateKeyInfoAsn privateKeyInfoAsn = PrivateKeyInfoAsn . Decode ( privateKeyInfo , AsnEncodingRules . BER ) ;
2152+
2153+ const X509KeyUsageFlags EcdsaKeyUsageFlags =
2154+ X509KeyUsageFlags . DigitalSignature |
2155+ X509KeyUsageFlags . KeyCertSign |
2156+ X509KeyUsageFlags . CrlSign ;
2157+
2158+ X509KeyUsageFlags ? usages = GetKeyUsageFlags ( in privateKeyInfoAsn ) ;
2159+
2160+ // If any of the following is true we should load it as EC-DH
2161+ // * There is no keyUsage extension. Loading it as EC-DH will allow loading it as EC-DSA, too.
2162+ // * It has any keyUsage that is not a "signing" usage. That at minimum means loading it as EC-DH.
2163+ // it may still yet have a keyUsage that allows signing as well, in which case it will work for EC-DSA
2164+ // too.
2165+ // The certificate must also have a key usage that permits EC-DH, either with "no" usage (in which case
2166+ // it will work for EC-DSA, too) or as EC-DH explicitly.
2167+ if ( ( usages is null || ( usages & ~ EcdsaKeyUsageFlags ) != 0 ) && IsECDiffieHellman ( certificate ) )
2168+ {
2169+ using ( ECDiffieHellman ecdh = ECDiffieHellman . Create ( ) )
2170+ {
2171+ ecdh . ImportPkcs8PrivateKey ( privateKeyInfo . Span , out int pkcs8Read ) ;
2172+
2173+ if ( pkcs8Read != privateKeyInfo . Length )
2174+ {
2175+ Debug . Fail ( "Unexpected trailing data in PKCS#8 buffer." ) ;
2176+ throw new CryptographicException ( ) ;
2177+ }
2178+
2179+ return certificate . CopyWithPrivateKey ( ecdh ) ;
2180+ }
2181+ }
2182+
2183+ // If we are here, then either the key or certificate has a key usage that requires loading it as EC-DSA.
2184+ if ( IsECDsa ( certificate ) )
2185+ {
2186+ using ( ECDsa ecdsa = ECDsa . Create ( ) )
2187+ {
2188+ ecdsa . ImportPkcs8PrivateKey ( privateKeyInfo . Span , out int pkcs8Read ) ;
2189+
2190+ if ( pkcs8Read != privateKeyInfo . Length )
2191+ {
2192+ Debug . Fail ( "Unexpected trailing data in PKCS#8 buffer." ) ;
2193+ throw new CryptographicException ( ) ;
2194+ }
2195+
2196+ return certificate . CopyWithPrivateKey ( ecdsa ) ;
2197+ }
2198+ }
2199+
2200+ // If we get here, the key and certificate do not agree on algorithm use (the key has digitalSignature but
2201+ // the certificate has keyAgreement, for example). It cannot be loaded.
2202+ return null ;
2203+ }
2204+
2205+ private static X509KeyUsageFlags ? GetKeyUsageFlags ( ref readonly PrivateKeyInfoAsn keyInfo )
2206+ {
2207+ if ( keyInfo . Attributes is null )
2208+ {
2209+ return null ;
2210+ }
2211+
2212+ foreach ( AttributeAsn attr in keyInfo . Attributes )
2213+ {
2214+ if ( attr . AttrType != Oids . KeyUsage )
2215+ {
2216+ continue ;
2217+ }
2218+
2219+ if ( attr . AttrValues is [ ReadOnlyMemory < byte > attrValue ] )
2220+ {
2221+ X509KeyUsageExtension . DecodeX509KeyUsageExtension ( attrValue . Span , out X509KeyUsageFlags usages ) ;
2222+ return usages ;
2223+ }
2224+
2225+ // If the attribute has no value or too many values, consider it malformed.
2226+ throw new CryptographicException ( SR . Cryptography_X509_NoOrMismatchedPemKey ) ;
2227+ }
2228+
2229+ return null ;
2230+ }
20122231 }
20132232}
0 commit comments