private static X509Certificate2 CreateRsaCertificate(byte[] cer, byte[] key) { if (!s_rsaInitializedImportPkcs8PrivateKeyMethod) { // ImportPkcs8PrivateKey was added in .NET Core 3.0 and is only present on Core. We will fall back to a lightweight decoder if this method is missing from the current runtime. s_rsaImportPkcs8PrivateKeyMethod = typeof(RSA).GetMethod("ImportPkcs8PrivateKey", BindingFlags.Instance | BindingFlags.Public, null, new[] { typeof(ReadOnlySpan <byte>), typeof(int).MakeByRefType() }, null); s_rsaInitializedImportPkcs8PrivateKeyMethod = true; } if (s_rsaCopyWithPrivateKeyMethod is null) { s_rsaCopyWithPrivateKeyMethod = typeof(RSACertificateExtensions).GetMethod("CopyWithPrivateKey", BindingFlags.Static | BindingFlags.Public, null, new[] { typeof(X509Certificate2), typeof(RSA) }, null) ?? throw new PlatformNotSupportedException("The current platform does not support reading a private key from a PEM file"); } RSA privateKey = null; try { if (s_rsaImportPkcs8PrivateKeyMethod != null) { privateKey = RSA.Create(); // Because ImportPkcs8PrivateKey declares an out parameter we cannot call it directly using MethodInfo.Invoke since all arguments are passed as an object array. // Instead we create a delegate with the correct signature and invoke it. ImportPrivateKeyDelegate importPkcs8PrivateKey = (ImportPrivateKeyDelegate)s_rsaImportPkcs8PrivateKeyMethod.CreateDelegate(typeof(ImportPrivateKeyDelegate), privateKey); importPkcs8PrivateKey.Invoke(key, out int bytesRead); if (key.Length != bytesRead) { throw new InvalidDataException("Invalid PKCS#8 Data"); } } else { privateKey = LightweightPkcs8Decoder.DecodeRSAPkcs8(key); } using X509Certificate2 certificateWithoutPrivateKey = new X509Certificate2(cer); X509Certificate2 certificate = (X509Certificate2)s_rsaCopyWithPrivateKeyMethod.Invoke(null, new object[] { certificateWithoutPrivateKey, privateKey }); // On .NET Framework the PrivateKey member is not initialized after calling CopyWithPrivateKey. if (certificate.PrivateKey is null) { certificate.PrivateKey = privateKey; } // Make sure the private key doesn't get disposed now that it's used. privateKey = null; return(certificate); } finally { // If we created and did not use the RSA private key, make sure it's disposed. privateKey?.Dispose(); } }
static partial void CreateECDsaCertificate(byte[] cer, byte[] key, X509KeyStorageFlags keyStorageFlags, ref X509Certificate2 certificate) { if (!s_ecInitializedImportPkcs8PrivateKeyMethod) { // ImportPkcs8PrivateKey was added in .NET Core 3.0 and is only present on Core. We will fall back to a lightweight decoder if this method is missing from the current runtime. s_ecImportPkcs8PrivateKeyMethod = typeof(ECDsa).GetMethod("ImportPkcs8PrivateKey", BindingFlags.Instance | BindingFlags.Public, null, new[] { typeof(ReadOnlySpan <byte>), typeof(int).MakeByRefType() }, null); s_ecInitializedImportPkcs8PrivateKeyMethod = true; } if (s_ecCopyWithPrivateKeyMethod is null) { s_ecCopyWithPrivateKeyMethod = typeof(ECDsaCertificateExtensions).GetMethod("CopyWithPrivateKey", BindingFlags.Static | BindingFlags.Public, null, new[] { typeof(X509Certificate2), typeof(ECDsa) }, null) ?? throw new PlatformNotSupportedException("The current platform does not support reading an ECDsa private key from a PEM file"); } // Create the certificate without the private key to pass to our PKCS8 decoder if needed to copy the prime curve. using X509Certificate2 certificateWithoutPrivateKey = new X509Certificate2(cer, (string)null, keyStorageFlags); ECDsa privateKey = null; try { if (s_ecImportPkcs8PrivateKeyMethod != null) { privateKey = ECDsa.Create(); // Because ImportECPrivateKeyMethod declares an out parameter we cannot call it directly using MethodInfo.Invoke since all arguments are passed as an object array. // Instead we create a delegate with the correct signature and invoke it. ImportPrivateKeyDelegate importECPrivateKey = (ImportPrivateKeyDelegate)s_ecImportPkcs8PrivateKeyMethod.CreateDelegate(typeof(ImportPrivateKeyDelegate), privateKey); importECPrivateKey.Invoke(key, out int bytesRead); if (key.Length != bytesRead) { throw new InvalidDataException("Invalid PKCS#8 Data"); } } else { // Copy the prime curve from the public key to mitigate risk parsing the ASN.1 structure ourselves. using ECDsa publicKey = certificateWithoutPrivateKey.GetECDsaPublicKey(); privateKey = LightweightPkcs8Decoder.DecodeECDsaPkcs8(key, publicKey); } certificate = (X509Certificate2)s_ecCopyWithPrivateKeyMethod.Invoke(null, new object[] { certificateWithoutPrivateKey, privateKey }); // Make sure the private key doesn't get disposed now that it's used. privateKey = null; } finally { // If we created and did not use the RSA private key, make sure it's disposed. privateKey?.Dispose(); } }
/// <summary> /// Loads an <see cref="X509Certificate2"/> from PEM data. /// </summary> /// <param name="data">The PEM data to parse.</param> /// <param name="cer">Optional public certificate data if not defined within the PEM data.</param> /// <param name="allowCertificateOnly">Whether to create an <see cref="X509Certificate2"/> if no private key is read.</param> /// <returns>An <see cref="X509Certificate2"/> loaded from the PEM data.</returns> /// <exception cref="CryptographicException">A cryptographic exception occurred when trying to create the <see cref="X509Certificate2"/>.</exception> /// <exception cref="InvalidDataException"><paramref name="cer"/> is null and no CERTIFICATE field is defined in PEM, or no PRIVATE KEY is defined in PEM.</exception> /// <exception cref="PlatformNotSupportedException">Creating a <see cref="X509Certificate2"/> from PEM data is not supported on the current platform.</exception> public static X509Certificate2 LoadCertificate(ReadOnlySpan <char> data, byte[] cer = null, bool allowCertificateOnly = false) { byte[] priv = null; while (TryRead(data, out PemField field)) { // TODO: Consider building up a chain to determine the leaf certificate: https://github.com/Azure/azure-sdk-for-net/issues/19043 if (field.Label.Equals("CERTIFICATE".AsSpan(), StringComparison.Ordinal)) { cer = field.FromBase64Data(); } else if (field.Label.Equals("PRIVATE KEY".AsSpan(), StringComparison.Ordinal)) { priv = field.FromBase64Data(); } int offset = field.Start + field.Length; if (offset >= data.Length) { break; } data = data.Slice(offset); } if (cer is null) { throw new InvalidDataException("The certificate is missing the public key"); } if (priv is null) { if (allowCertificateOnly) { return(new X509Certificate2(cer)); } throw new InvalidDataException("The certificate is missing the private key"); } if (!s_initializedImportPkcs8PrivateKeyMethod) { // ImportPkcs8PrivateKey was added in .NET Core 3.0 and is only present on Core. We will fall back to a lightweight decoder if this method is missing from the current runtime. s_importPkcs8PrivateKeyMethod = typeof(RSA).GetMethod("ImportPkcs8PrivateKey", BindingFlags.Instance | BindingFlags.Public, null, new[] { typeof(ReadOnlySpan <byte>), typeof(int).MakeByRefType() }, null); s_initializedImportPkcs8PrivateKeyMethod = true; } if (s_copyWithPrivateKeyMethod is null) { s_copyWithPrivateKeyMethod = typeof(RSACertificateExtensions).GetMethod("CopyWithPrivateKey", BindingFlags.Static | BindingFlags.Public, null, new[] { typeof(X509Certificate2), typeof(RSA) }, null) ?? throw new PlatformNotSupportedException("The current platform does not support reading a private key from a PEM file"); } RSA privateKey = null; try { if (s_importPkcs8PrivateKeyMethod != null) { privateKey = RSA.Create(); // Because ImportPkcs8PrivateKey declares an out parameter we cannot call it directly using MethodInfo.Invoke since all arguments are passed as an object array. // Instead we create a delegate with the correct signature and invoke it. ImportPkcs8PrivateKeyDelegate importPkcs8PrivateKey = (ImportPkcs8PrivateKeyDelegate)s_importPkcs8PrivateKeyMethod.CreateDelegate(typeof(ImportPkcs8PrivateKeyDelegate), privateKey); importPkcs8PrivateKey.Invoke(priv, out _); } else { privateKey = LightweightPkcs8Decoder.DecodeRSAPkcs8(priv); } using X509Certificate2 certificateWithoutPrivateKey = new X509Certificate2(cer); X509Certificate2 certificate = (X509Certificate2)s_copyWithPrivateKeyMethod.Invoke(null, new object[] { certificateWithoutPrivateKey, privateKey }); // On .NET Framework the PrivateKey member is not initialized after calling CopyWithPRivateKey. if (certificate.PrivateKey is null) { certificate.PrivateKey = privateKey; } // Make sure the private key doesn't get disposed now that it's used. privateKey = null; return(certificate); } finally { // If we created and did not use the RSA private key, make sure it's disposed. privateKey?.Dispose(); } }
/// <summary> /// Loads an <see cref="X509Certificate2"/> from PEM data. /// </summary> /// <param name="data">The PEM data to parse.</param> /// <param name="cer">Optional public certificate data if not defined within the PEM data.</param> /// <param name="keyType"> /// Optional <see cref="KeyType"/> of the certificate private key. The default is <see cref="KeyType.Auto"/> to automatically detect. /// Only support for <see cref="KeyType.RSA"/> is implemented by shared code. /// </param> /// <param name="allowCertificateOnly">Whether to create an <see cref="X509Certificate2"/> if no private key is read.</param> /// <returns>An <see cref="X509Certificate2"/> loaded from the PEM data.</returns> /// <exception cref="CryptographicException">A cryptographic exception occurred when trying to create the <see cref="X509Certificate2"/>.</exception> /// <exception cref="InvalidDataException"><paramref name="cer"/> is null and no CERTIFICATE field is defined in PEM, or no PRIVATE KEY is defined in PEM.</exception> /// <exception cref="NotSupportedException">The <paramref name="keyType"/> is not supported.</exception> /// <exception cref="PlatformNotSupportedException">Creating a <see cref="X509Certificate2"/> from PEM data is not supported on the current platform.</exception> public static X509Certificate2 LoadCertificate(ReadOnlySpan <char> data, byte[] cer = null, KeyType keyType = KeyType.Auto, bool allowCertificateOnly = false) { byte[] priv = null; while (TryRead(data, out PemField field)) { // TODO: Consider building up a chain to determine the leaf certificate: https://github.com/Azure/azure-sdk-for-net/issues/19043 if (field.Label.Equals("CERTIFICATE".AsSpan(), StringComparison.Ordinal)) { cer = field.FromBase64Data(); } else if (field.Label.Equals("PRIVATE KEY".AsSpan(), StringComparison.Ordinal)) { priv = field.FromBase64Data(); } int offset = field.Start + field.Length; if (offset >= data.Length) { break; } data = data.Slice(offset); } if (cer is null) { throw new InvalidDataException("The certificate is missing the public key"); } if (priv is null) { if (allowCertificateOnly) { return(new X509Certificate2(cer)); } throw new InvalidDataException("The certificate is missing the private key"); } if (keyType == KeyType.Auto) { string oid = LightweightPkcs8Decoder.DecodePrivateKeyOid(priv); keyType = oid switch { RSAAlgorithmId => KeyType.RSA, ECDsaAlgorithmId => KeyType.ECDsa, _ => throw new NotSupportedException($"The private key algorithm ID {oid} is not supported"), }; } if (keyType == KeyType.ECDsa) { X509Certificate2 certificate = null; CreateECDsaCertificate(cer, priv, ref certificate); return(certificate ?? throw new NotSupportedException("Reading an ECDsa certificate from a PEM file is not supported")); } return(CreateRsaCertificate(cer, priv)); }