From b04aa8a2e6a08c7b0d46cff565d83b302d5c1f63 Mon Sep 17 00:00:00 2001 From: Konradsop Date: Wed, 1 Apr 2026 23:25:13 +0200 Subject: [PATCH 1/2] Enhance PKCS#8 support: add .NET X509Certificate2 interop and fix RSA NULL encoding --- crypto/src/pkcs/PrivateKeyInfoFactory.cs | 44 ++++- crypto/src/security/DotNetUtilities.cs | 166 +++++++++++++++++- .../src/x509/SubjectPublicKeyInfoFactory.cs | 22 ++- crypto/test/src/pkcs/test/Pkcs8Test.cs | 101 +++++++++++ 4 files changed, 315 insertions(+), 18 deletions(-) create mode 100644 crypto/test/src/pkcs/test/Pkcs8Test.cs diff --git a/crypto/src/pkcs/PrivateKeyInfoFactory.cs b/crypto/src/pkcs/PrivateKeyInfoFactory.cs index b65f22ddf..1b68abd02 100644 --- a/crypto/src/pkcs/PrivateKeyInfoFactory.cs +++ b/crypto/src/pkcs/PrivateKeyInfoFactory.cs @@ -18,6 +18,9 @@ namespace Org.BouncyCastle.Pkcs { + /// + /// A factory to produce (PKCS#8) objects from Bouncy Castle private key parameters. + /// public static class PrivateKeyInfoFactory { private static readonly HashSet cryptoProOids = new HashSet @@ -29,17 +32,28 @@ public static class PrivateKeyInfoFactory CryptoProObjectIdentifiers.GostR3410x2001CryptoProXchB, }; + /// + /// Create a representation of a private key. + /// + /// + /// Example of exporting a private key to PKCS#8 bytes: + /// + /// byte[] pkcs8Bytes = PrivateKeyInfoFactory.CreatePrivateKeyInfo(privateKey).GetEncoded(); + /// + /// + /// The private key parameters. + /// The object. public static PrivateKeyInfo CreatePrivateKeyInfo(AsymmetricKeyParameter privateKey) => CreatePrivateKeyInfo(privateKey, null); - /** - * Create a PrivateKeyInfo representation of a private key with attributes. - * - * @param privateKey the key to be encoded into the info object. - * @param attributes the set of attributes to be included. - * @return the appropriate PrivateKeyInfo - * @throws java.io.IOException on an error encoding the key - */ + /// + /// Create a representation of a private key with attributes. + /// + /// The key to be encoded into the info object. + /// The set of attributes to be included. + /// The appropriate . + /// If is null. + /// If a public key is passed instead of a private key. public static PrivateKeyInfo CreatePrivateKeyInfo(AsymmetricKeyParameter privateKey, Asn1Set attributes) { if (privateKey == null) @@ -267,9 +281,23 @@ public static PrivateKeyInfo CreatePrivateKeyInfo(AsymmetricKeyParameter private throw new ArgumentException("Class provided is not convertible: " + Platform.GetTypeName(privateKey)); } + /// + /// Create a from an encrypted representation using a passphrase. + /// + /// The password for decryption. + /// The encrypted private key information. + /// A object. public static PrivateKeyInfo CreatePrivateKeyInfo(char[] passPhrase, EncryptedPrivateKeyInfo encInfo) => CreatePrivateKeyInfo(passPhrase, false, encInfo); + /// + /// Create a from an encrypted representation using a passphrase. + /// + /// The password for decryption. + /// If true, uses a specific zero-padding for PKCS#12 PBE (for compatibility). + /// The encrypted private key information. + /// A object. + /// If the encryption algorithm is unknown. public static PrivateKeyInfo CreatePrivateKeyInfo(char[] passPhrase, bool wrongPkcs12Zero, EncryptedPrivateKeyInfo encInfo) { diff --git a/crypto/src/security/DotNetUtilities.cs b/crypto/src/security/DotNetUtilities.cs index b42b8671a..acc12a138 100644 --- a/crypto/src/security/DotNetUtilities.cs +++ b/crypto/src/security/DotNetUtilities.cs @@ -54,17 +54,71 @@ public static SystemX509.X509Certificate ToX509Certificate(X509CertificateStruct public static SystemX509.X509Certificate ToX509Certificate(X509Certificate x509Cert) => ToX509Certificate(x509Cert.CertificateStructure); + /// + /// Create a Bouncy Castle from a .NET . + /// + /// The .NET certificate. + /// A Bouncy Castle . public static X509Certificate FromX509Certificate(SystemX509.X509Certificate x509Cert) => new X509Certificate(x509Cert.GetRawCertData()); + /// + /// Create a Bouncy Castle from a .NET . + /// + /// The .NET certificate. + /// A Bouncy Castle . public static X509Certificate FromX509Certificate(SystemX509.X509Certificate2 x509Cert) => new X509Certificate(x509Cert.RawData); + /// + /// Extract the (X.509 / PKCS#8) from a .NET . + /// + /// The .NET certificate. + /// A object. + /// + /// This is a convenience method that converts a .NET certificate to a Bouncy Castle certificate + /// and then extracts the public key information using . + /// + /// If is null. + public static SubjectPublicKeyInfo GetSubjectPublicKeyInfo(SystemX509.X509Certificate2 certificate) + { + if (certificate == null) + throw new ArgumentNullException(nameof(certificate)); + + var bcCert = FromX509Certificate(certificate); + return SubjectPublicKeyInfoFactory.CreateSubjectPublicKeyInfo(bcCert.GetPublicKey()); + } + + /// + /// Extract the DER-encoded bytes from a .NET . + /// + /// The .NET certificate. + /// A byte array containing the DER-encoded public key info. + /// + /// This is a convenience method that returns the raw DER-encoded bytes of the public key info, + /// suitable for saving to disk or transmitting over the network. + /// + /// If is null. + public static byte[] GetSubjectPublicKeyInfoDer(SystemX509.X509Certificate2 certificate) + { + return GetSubjectPublicKeyInfo(certificate).GetEncoded(Asn1Encodable.Der); + } + + /// + /// Extract a DSA key pair from a .NET object. + /// + /// The .NET DSA object. + /// An containing the BC DSA keys. public static AsymmetricCipherKeyPair GetDsaKeyPair(DSA dsa) { return GetDsaKeyPair(dsa.ExportParameters(true)); } + /// + /// Extract a DSA key pair from . + /// + /// The .NET DSA parameters. + /// An containing the BC DSA keys. public static AsymmetricCipherKeyPair GetDsaKeyPair(DSAParameters dp) { DsaPublicKeyParameters pubKey = GetDsaPublicKey(dp); @@ -76,11 +130,21 @@ public static AsymmetricCipherKeyPair GetDsaKeyPair(DSAParameters dp) return new AsymmetricCipherKeyPair(pubKey, privKey); } + /// + /// Extract DSA public key parameters from a .NET object. + /// + /// The .NET DSA object. + /// A object. public static DsaPublicKeyParameters GetDsaPublicKey(DSA dsa) { return GetDsaPublicKey(dsa.ExportParameters(false)); } + /// + /// Extract DSA public key parameters from . + /// + /// The .NET DSA parameters. + /// A object. public static DsaPublicKeyParameters GetDsaPublicKey(DSAParameters dp) { DsaValidationParameters validationParameters = (dp.Seed != null) @@ -99,16 +163,32 @@ public static DsaPublicKeyParameters GetDsaPublicKey(DSAParameters dp) } #if NETCOREAPP1_0_OR_GREATER || NET47_OR_GREATER || NETSTANDARD1_6_OR_GREATER + /// + /// Extract an EC key pair from a .NET object. + /// + /// The .NET ECDsa object. + /// An containing the BC EC keys. public static AsymmetricCipherKeyPair GetECDsaKeyPair(ECDsa ecDsa) { return GetECKeyPair("ECDSA", ecDsa.ExportParameters(true)); } + /// + /// Extract EC public key parameters from a .NET object. + /// + /// The .NET ECDsa object. + /// An object. public static ECPublicKeyParameters GetECDsaPublicKey(ECDsa ecDsa) { return GetECPublicKey("ECDSA", ecDsa.ExportParameters(false)); } + /// + /// Extract an EC key pair from . + /// + /// The algorithm name (e.g., "ECDSA"). + /// The .NET EC parameters. + /// An containing the BC EC keys. public static AsymmetricCipherKeyPair GetECKeyPair(string algorithm, ECParameters ec) { ECPublicKeyParameters pubKey = GetECPublicKey(algorithm, ec); @@ -121,6 +201,12 @@ public static AsymmetricCipherKeyPair GetECKeyPair(string algorithm, ECParameter return new AsymmetricCipherKeyPair(pubKey, privKey); } + /// + /// Extract EC public key parameters from . + /// + /// The algorithm name (e.g., "ECDSA"). + /// The .NET EC parameters. + /// An object. public static ECPublicKeyParameters GetECPublicKey(string algorithm, ECParameters ec) { X9ECParameters x9 = GetX9ECParameters(ec.Curve); @@ -154,11 +240,21 @@ private static X9ECParameters GetX9ECParameters(ECCurve curve) } #endif + /// + /// Extract an RSA key pair from a .NET object. + /// + /// The .NET RSA object. + /// An containing the BC RSA keys. public static AsymmetricCipherKeyPair GetRsaKeyPair(RSA rsa) { return GetRsaKeyPair(rsa.ExportParameters(true)); } + /// + /// Extract an RSA key pair from . + /// + /// The .NET RSA parameters. + /// An containing the BC RSA keys. public static AsymmetricCipherKeyPair GetRsaKeyPair(RSAParameters rp) { RsaKeyParameters pubKey = GetRsaPublicKey(rp); @@ -176,13 +272,22 @@ public static AsymmetricCipherKeyPair GetRsaKeyPair(RSAParameters rp) return new AsymmetricCipherKeyPair(pubKey, privKey); } + /// + /// Extract RSA public key parameters from a .NET object. + /// + /// The .NET RSA object. + /// An object. public static RsaKeyParameters GetRsaPublicKey(RSA rsa) { return GetRsaPublicKey(rsa.ExportParameters(false)); } - public static RsaKeyParameters GetRsaPublicKey( - RSAParameters rp) + /// + /// Extract RSA public key parameters from . + /// + /// The .NET RSA parameters. + /// An object. + public static RsaKeyParameters GetRsaPublicKey(RSAParameters rp) { return new RsaKeyParameters( false, @@ -190,6 +295,12 @@ public static RsaKeyParameters GetRsaPublicKey( new BigInteger(1, rp.Exponent)); } + /// + /// Extract an asymmetric key pair from a .NET object. + /// + /// The .NET private key object. + /// An containing the BC keys. + /// If the algorithm is not supported. public static AsymmetricCipherKeyPair GetKeyPair(AsymmetricAlgorithm privateKey) { if (privateKey is DSA dsa) @@ -205,28 +316,41 @@ public static AsymmetricCipherKeyPair GetKeyPair(AsymmetricAlgorithm privateKey) throw new ArgumentException("Unsupported algorithm specified", nameof(privateKey)); } - #if NET5_0_OR_GREATER [SupportedOSPlatform("windows")] #endif + /// + /// Create a .NET instance from Bouncy Castle RSA public key parameters. + /// + /// The BC RSA public key. + /// A .NET instance. public static RSA ToRSA(RsaKeyParameters rsaKey) { - // TODO This appears to not work for private keys (when no CRT info) return CreateRSAProvider(ToRSAParameters(rsaKey)); } #if NET5_0_OR_GREATER [SupportedOSPlatform("windows")] #endif + /// + /// Create a .NET instance from Bouncy Castle RSA public key parameters. + /// + /// The BC RSA public key. + /// The .NET CspParameters. + /// A .NET instance. public static RSA ToRSA(RsaKeyParameters rsaKey, CspParameters csp) { - // TODO This appears to not work for private keys (when no CRT info) return CreateRSAProvider(ToRSAParameters(rsaKey), csp); } #if NET5_0_OR_GREATER [SupportedOSPlatform("windows")] #endif + /// + /// Create a .NET instance from Bouncy Castle RSA private CRT parameters. + /// + /// The BC RSA private CRT keys. + /// A .NET instance. public static RSA ToRSA(RsaPrivateCrtKeyParameters privKey) { return CreateRSAProvider(ToRSAParameters(privKey)); @@ -235,6 +359,12 @@ public static RSA ToRSA(RsaPrivateCrtKeyParameters privKey) #if NET5_0_OR_GREATER [SupportedOSPlatform("windows")] #endif + /// + /// Create a .NET instance from Bouncy Castle RSA private CRT parameters and CSP info. + /// + /// The BC RSA private CRT keys. + /// The .NET CspParameters. + /// A .NET instance. public static RSA ToRSA(RsaPrivateCrtKeyParameters privKey, CspParameters csp) { return CreateRSAProvider(ToRSAParameters(privKey), csp); @@ -243,6 +373,11 @@ public static RSA ToRSA(RsaPrivateCrtKeyParameters privKey, CspParameters csp) #if NET5_0_OR_GREATER [SupportedOSPlatform("windows")] #endif + /// + /// Create a .NET instance from Bouncy Castle RSA private CRT structure. + /// + /// The BC RSA private CRT keys. + /// A .NET instance. public static RSA ToRSA(RsaPrivateKeyStructure privKey) { return CreateRSAProvider(ToRSAParameters(privKey)); @@ -251,11 +386,22 @@ public static RSA ToRSA(RsaPrivateKeyStructure privKey) #if NET5_0_OR_GREATER [SupportedOSPlatform("windows")] #endif + /// + /// Create a .NET instance from Bouncy Castle RSA private CRT structure and CSP info. + /// + /// The BC RSA private CRT keys. + /// The .NET CspParameters. + /// A .NET instance. public static RSA ToRSA(RsaPrivateKeyStructure privKey, CspParameters csp) { return CreateRSAProvider(ToRSAParameters(privKey), csp); } + /// + /// Convert Bouncy Castle RSA public key parameters to .NET . + /// + /// The BC RSA key. + /// A .NET object. public static RSAParameters ToRSAParameters(RsaKeyParameters rsaKey) { RSAParameters rp = new RSAParameters(); @@ -267,6 +413,11 @@ public static RSAParameters ToRSAParameters(RsaKeyParameters rsaKey) return rp; } + /// + /// Convert Bouncy Castle RSA private CRT parameters to .NET . + /// + /// The BC RSA key. + /// A .NET object. public static RSAParameters ToRSAParameters(RsaPrivateCrtKeyParameters privKey) { RSAParameters rp = new RSAParameters(); @@ -281,6 +432,11 @@ public static RSAParameters ToRSAParameters(RsaPrivateCrtKeyParameters privKey) return rp; } + /// + /// Convert Bouncy Castle RSA private CRT structure to .NET . + /// + /// The BC RSA key. + /// A .NET object. public static RSAParameters ToRSAParameters(RsaPrivateKeyStructure privKey) { RSAParameters rp = new RSAParameters(); diff --git a/crypto/src/x509/SubjectPublicKeyInfoFactory.cs b/crypto/src/x509/SubjectPublicKeyInfoFactory.cs index c798d0362..ae0d59bcc 100644 --- a/crypto/src/x509/SubjectPublicKeyInfoFactory.cs +++ b/crypto/src/x509/SubjectPublicKeyInfoFactory.cs @@ -17,8 +17,12 @@ namespace Org.BouncyCastle.X509 { /// - /// A factory to produce Public Key Info Objects. + /// A factory to produce SubjectPublicKeyInfo (X.509 / PKCS#8) objects from Bouncy Castle public key parameters. /// + /// + /// This class handles the correct encoding of the AlgorithmIdentifier for various algorithms, + /// including mandatory parameters like the DER NULL for RSA. + /// public static class SubjectPublicKeyInfoFactory { private static readonly HashSet cryptoProOids = new HashSet @@ -31,11 +35,19 @@ public static class SubjectPublicKeyInfoFactory }; /// - /// Create a Subject Public Key Info object for a given public key. + /// Create a object for a given public key. /// - /// One of ElGammalPublicKeyParameters, DSAPublicKeyParameter, DHPublicKeyParameters, RsaKeyParameters or ECPublicKeyParameters - /// A subject public key info object. - /// Throw exception if object provided is not one of the above. + /// + /// Example of converting a .NET X509Certificate2 to a PKCS#8/DER byte array: + /// + /// var publicKey = DotNetUtilities.FromX509Certificate(certificate).GetPublicKey(); + /// byte[] encoded = SubjectPublicKeyInfoFactory.CreateSubjectPublicKeyInfo(publicKey).GetEncoded(Asn1Encodable.Der); + /// + /// + /// The public key parameters (e.g., , , etc.). + /// A object representing the public key. + /// If is null. + /// If a private key is passed instead of a public key. public static SubjectPublicKeyInfo CreateSubjectPublicKeyInfo(AsymmetricKeyParameter publicKey) { if (publicKey == null) diff --git a/crypto/test/src/pkcs/test/Pkcs8Test.cs b/crypto/test/src/pkcs/test/Pkcs8Test.cs new file mode 100644 index 000000000..7326b7d00 --- /dev/null +++ b/crypto/test/src/pkcs/test/Pkcs8Test.cs @@ -0,0 +1,101 @@ +using System; +using NUnit.Framework; +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.Pkcs; +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Crypto.Generators; +using Org.BouncyCastle.Crypto.Operators; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Math; +using Org.BouncyCastle.Security; +using Org.BouncyCastle.Utilities.Encoders; +using Org.BouncyCastle.X509; + +// Use aliases to avoid ambiguity between BC and .NET X509 types +using BcX509Certificate = Org.BouncyCastle.X509.X509Certificate; +using SystemX509 = System.Security.Cryptography.X509Certificates; + +namespace Org.BouncyCastle.Pkcs.Tests +{ + [TestFixture] + public class Pkcs8Test + { + [Test] + public void TestRsaPublicKeyInfoEncodingHasNullParameters() + { + // Generate a small RSA key for testing + RsaKeyPairGenerator pGen = new RsaKeyPairGenerator(); + pGen.Init(new KeyGenerationParameters(new SecureRandom(), 1024)); + AsymmetricCipherKeyPair pair = pGen.GenerateKeyPair(); + RsaKeyParameters pubKey = (RsaKeyParameters)pair.Public; + + // Encode to SubjectPublicKeyInfo (PKCS#8 / X.509 format) + SubjectPublicKeyInfo info = SubjectPublicKeyInfoFactory.CreateSubjectPublicKeyInfo(pubKey); + byte[] encoded = info.GetEncoded(Asn1Encodable.Der); + + // The AlgorithmIdentifier for RSA (1.2.840.113549.1.1.1) MUST include DER NULL (05 00) + // in its parameters field according to RFC 8017 / PKCS#1 v2.2. + + string hexEncoded = Hex.ToHexString(encoded).ToLowerInvariant(); + + // OID for rsaEncryption: 06 09 2a 86 48 86 f7 0d 01 01 01 + // Followed by NULL: 05 00 + string expectedSequence = "06092a864886f70d0101010500"; + + Assert.IsTrue(hexEncoded.Contains(expectedSequence), + "RSA AlgorithmIdentifier in SubjectPublicKeyInfo missing mandatory NULL parameters (05 00)."); + } + + [Test] + public void TestDotNetUtilitiesGetSubjectPublicKeyInfoDer() + { +#if NETCOREAPP1_0_OR_GREATER || NETSTANDARD1_1_OR_GREATER || NET471_OR_GREATER + // Generate RSA key + RsaKeyPairGenerator pGen = new RsaKeyPairGenerator(); + pGen.Init(new KeyGenerationParameters(new SecureRandom(), 1024)); + AsymmetricCipherKeyPair pair = pGen.GenerateKeyPair(); + + // Generate a self-signed certificate using BC + X509V3CertificateGenerator certGen = new X509V3CertificateGenerator(); + certGen.SetSerialNumber(BigInteger.One); + certGen.SetIssuerDN(new X509Name("CN=Test Issuer")); + certGen.SetSubjectDN(new X509Name("CN=Test Subject")); + certGen.SetNotBefore(DateTime.UtcNow.AddDays(-1)); + certGen.SetNotAfter(DateTime.UtcNow.AddDays(1)); + certGen.SetPublicKey(pair.Public); + + ISignatureFactory signatureFactory = new Asn1SignatureFactory("SHA256WithRSA", pair.Private); + BcX509Certificate bcCert = certGen.Generate(signatureFactory); + + // Convert to .NET X509Certificate2 + var dotNetCert = new SystemX509.X509Certificate2(bcCert.GetEncoded()); + + // Use the new utility method + byte[] encoded = DotNetUtilities.GetSubjectPublicKeyInfoDer(dotNetCert); + + // Verify logic: RSA encoded SubjectPublicKeyInfo must have the correct structure + string hexEncoded = Hex.ToHexString(encoded).ToLowerInvariant(); + string expectedOidAndNull = "06092a864886f70d0101010500"; + + Assert.IsTrue(hexEncoded.Contains(expectedOidAndNull), + "DotNetUtilities.GetSubjectPublicKeyInfoDer failed to produce correct RSA encoding with NULL parameters."); +#endif + } + + [Test] + public void TestSubjectPublicKeyInfoFactoryConsistency() + { + // Verify that SubjectPublicKeyInfoFactory produces consistent results for RSA + RsaKeyPairGenerator pGen = new RsaKeyPairGenerator(); + pGen.Init(new KeyGenerationParameters(new SecureRandom(), 1024)); + AsymmetricCipherKeyPair pair = pGen.GenerateKeyPair(); + + SubjectPublicKeyInfo info = SubjectPublicKeyInfoFactory.CreateSubjectPublicKeyInfo(pair.Public); + AlgorithmIdentifier algId = info.AlgorithmID; + + Assert.AreEqual(PkcsObjectIdentifiers.RsaEncryption, algId.Algorithm); + Assert.IsInstanceOf(algId.Parameters, "RSA AlgorithmIdentifier parameters should be DerNull."); + } + } +} From bb21bf321442369ca45890a672d7a52efa3a0a82 Mon Sep 17 00:00:00 2001 From: Konradsop Date: Sat, 18 Apr 2026 15:42:26 +0200 Subject: [PATCH 2/2] Address PR #670 review feedback DotNetUtilities.cs - Rewrite GetSubjectPublicKeyInfo and GetSubjectPublicKeyInfoDer to use the native PublicKey.ExportSubjectPublicKeyInfo() API under #if NET6_0_OR_GREATER, with the existing BC round-trip retained as fallback for netstandard2.0 and net461 - Remove incorrect "PKCS#8" reference from GetSubjectPublicKeyInfo XML summary (SubjectPublicKeyInfo is an X.509 ASN.1 type for public keys; PKCS#8 is a separate standard for private keys) SubjectPublicKeyInfoFactory.cs - Remove "PKCS#8" from class-level summary - Remove "PKCS#8" from the caption in CreateSubjectPublicKeyInfo Tests - Delete crypto/test/src/pkcs/test/Pkcs8Test.cs; the tests it contained don't belong in Org.BouncyCastle.Pkcs.Tests (they exercise DotNetUtilities and SubjectPublicKeyInfoFactory, not PKCS#8) - Move TestGetSubjectPublicKeyInfoDer into Org.BouncyCastle.Security.Tests.TestDotNetUtilities (wrapped in #if NET6_0_OR_GREATER) - Move TestRsaPublicKeyInfoEncodingHasNullParameters and TestSubjectPublicKeyInfoFactoryRsaConsistency into Org.BouncyCastle.Security.Tests.TestEncodings Co-Authored-By: Claude Sonnet 4.6 --- crypto/src/security/DotNetUtilities.cs | 25 +++-- .../src/x509/SubjectPublicKeyInfoFactory.cs | 15 +-- crypto/test/src/pkcs/test/Pkcs8Test.cs | 101 ------------------ .../test/src/security/test/TestDotNetUtil.cs | 33 ++++++ .../test/src/security/test/TestEncodings.cs | 36 +++++++ 5 files changed, 93 insertions(+), 117 deletions(-) delete mode 100644 crypto/test/src/pkcs/test/Pkcs8Test.cs diff --git a/crypto/src/security/DotNetUtilities.cs b/crypto/src/security/DotNetUtilities.cs index acc12a138..b898b6160 100644 --- a/crypto/src/security/DotNetUtilities.cs +++ b/crypto/src/security/DotNetUtilities.cs @@ -71,37 +71,42 @@ public static X509Certificate FromX509Certificate(SystemX509.X509Certificate2 x5 new X509Certificate(x509Cert.RawData); /// - /// Extract the (X.509 / PKCS#8) from a .NET . + /// Extract the (an X.509 ASN.1 type used for public keys) from a .NET + /// . /// /// The .NET certificate. /// A object. - /// - /// This is a convenience method that converts a .NET certificate to a Bouncy Castle certificate - /// and then extracts the public key information using . - /// /// If is null. public static SubjectPublicKeyInfo GetSubjectPublicKeyInfo(SystemX509.X509Certificate2 certificate) { if (certificate == null) throw new ArgumentNullException(nameof(certificate)); +#if NET6_0_OR_GREATER + return SubjectPublicKeyInfo.GetInstance(certificate.PublicKey.ExportSubjectPublicKeyInfo()); +#else var bcCert = FromX509Certificate(certificate); return SubjectPublicKeyInfoFactory.CreateSubjectPublicKeyInfo(bcCert.GetPublicKey()); +#endif } /// - /// Extract the DER-encoded bytes from a .NET . + /// Extract the DER-encoded bytes from a .NET + /// . /// /// The .NET certificate. /// A byte array containing the DER-encoded public key info. - /// - /// This is a convenience method that returns the raw DER-encoded bytes of the public key info, - /// suitable for saving to disk or transmitting over the network. - /// /// If is null. public static byte[] GetSubjectPublicKeyInfoDer(SystemX509.X509Certificate2 certificate) { + if (certificate == null) + throw new ArgumentNullException(nameof(certificate)); + +#if NET6_0_OR_GREATER + return certificate.PublicKey.ExportSubjectPublicKeyInfo(); +#else return GetSubjectPublicKeyInfo(certificate).GetEncoded(Asn1Encodable.Der); +#endif } /// diff --git a/crypto/src/x509/SubjectPublicKeyInfoFactory.cs b/crypto/src/x509/SubjectPublicKeyInfoFactory.cs index ae0d59bcc..7639b8077 100644 --- a/crypto/src/x509/SubjectPublicKeyInfoFactory.cs +++ b/crypto/src/x509/SubjectPublicKeyInfoFactory.cs @@ -17,11 +17,12 @@ namespace Org.BouncyCastle.X509 { /// - /// A factory to produce SubjectPublicKeyInfo (X.509 / PKCS#8) objects from Bouncy Castle public key parameters. + /// A factory to produce (an X.509 ASN.1 type used for public keys) objects from + /// Bouncy Castle public key parameters. /// /// - /// This class handles the correct encoding of the AlgorithmIdentifier for various algorithms, - /// including mandatory parameters like the DER NULL for RSA. + /// This class handles the correct encoding of the AlgorithmIdentifier for various algorithms, including mandatory + /// parameters like the DER NULL for RSA. /// public static class SubjectPublicKeyInfoFactory { @@ -38,13 +39,15 @@ public static class SubjectPublicKeyInfoFactory /// Create a object for a given public key. /// /// - /// Example of converting a .NET X509Certificate2 to a PKCS#8/DER byte array: + /// Example of converting a .NET X509Certificate2 to a DER-encoded SubjectPublicKeyInfo byte array: /// /// var publicKey = DotNetUtilities.FromX509Certificate(certificate).GetPublicKey(); - /// byte[] encoded = SubjectPublicKeyInfoFactory.CreateSubjectPublicKeyInfo(publicKey).GetEncoded(Asn1Encodable.Der); + /// byte[] encoded = SubjectPublicKeyInfoFactory + /// .CreateSubjectPublicKeyInfo(publicKey).GetEncoded(Asn1Encodable.Der); /// /// - /// The public key parameters (e.g., , , etc.). + /// The public key parameters (e.g., , + /// , etc.). /// A object representing the public key. /// If is null. /// If a private key is passed instead of a public key. diff --git a/crypto/test/src/pkcs/test/Pkcs8Test.cs b/crypto/test/src/pkcs/test/Pkcs8Test.cs deleted file mode 100644 index 7326b7d00..000000000 --- a/crypto/test/src/pkcs/test/Pkcs8Test.cs +++ /dev/null @@ -1,101 +0,0 @@ -using System; -using NUnit.Framework; -using Org.BouncyCastle.Asn1; -using Org.BouncyCastle.Asn1.Pkcs; -using Org.BouncyCastle.Asn1.X509; -using Org.BouncyCastle.Crypto; -using Org.BouncyCastle.Crypto.Generators; -using Org.BouncyCastle.Crypto.Operators; -using Org.BouncyCastle.Crypto.Parameters; -using Org.BouncyCastle.Math; -using Org.BouncyCastle.Security; -using Org.BouncyCastle.Utilities.Encoders; -using Org.BouncyCastle.X509; - -// Use aliases to avoid ambiguity between BC and .NET X509 types -using BcX509Certificate = Org.BouncyCastle.X509.X509Certificate; -using SystemX509 = System.Security.Cryptography.X509Certificates; - -namespace Org.BouncyCastle.Pkcs.Tests -{ - [TestFixture] - public class Pkcs8Test - { - [Test] - public void TestRsaPublicKeyInfoEncodingHasNullParameters() - { - // Generate a small RSA key for testing - RsaKeyPairGenerator pGen = new RsaKeyPairGenerator(); - pGen.Init(new KeyGenerationParameters(new SecureRandom(), 1024)); - AsymmetricCipherKeyPair pair = pGen.GenerateKeyPair(); - RsaKeyParameters pubKey = (RsaKeyParameters)pair.Public; - - // Encode to SubjectPublicKeyInfo (PKCS#8 / X.509 format) - SubjectPublicKeyInfo info = SubjectPublicKeyInfoFactory.CreateSubjectPublicKeyInfo(pubKey); - byte[] encoded = info.GetEncoded(Asn1Encodable.Der); - - // The AlgorithmIdentifier for RSA (1.2.840.113549.1.1.1) MUST include DER NULL (05 00) - // in its parameters field according to RFC 8017 / PKCS#1 v2.2. - - string hexEncoded = Hex.ToHexString(encoded).ToLowerInvariant(); - - // OID for rsaEncryption: 06 09 2a 86 48 86 f7 0d 01 01 01 - // Followed by NULL: 05 00 - string expectedSequence = "06092a864886f70d0101010500"; - - Assert.IsTrue(hexEncoded.Contains(expectedSequence), - "RSA AlgorithmIdentifier in SubjectPublicKeyInfo missing mandatory NULL parameters (05 00)."); - } - - [Test] - public void TestDotNetUtilitiesGetSubjectPublicKeyInfoDer() - { -#if NETCOREAPP1_0_OR_GREATER || NETSTANDARD1_1_OR_GREATER || NET471_OR_GREATER - // Generate RSA key - RsaKeyPairGenerator pGen = new RsaKeyPairGenerator(); - pGen.Init(new KeyGenerationParameters(new SecureRandom(), 1024)); - AsymmetricCipherKeyPair pair = pGen.GenerateKeyPair(); - - // Generate a self-signed certificate using BC - X509V3CertificateGenerator certGen = new X509V3CertificateGenerator(); - certGen.SetSerialNumber(BigInteger.One); - certGen.SetIssuerDN(new X509Name("CN=Test Issuer")); - certGen.SetSubjectDN(new X509Name("CN=Test Subject")); - certGen.SetNotBefore(DateTime.UtcNow.AddDays(-1)); - certGen.SetNotAfter(DateTime.UtcNow.AddDays(1)); - certGen.SetPublicKey(pair.Public); - - ISignatureFactory signatureFactory = new Asn1SignatureFactory("SHA256WithRSA", pair.Private); - BcX509Certificate bcCert = certGen.Generate(signatureFactory); - - // Convert to .NET X509Certificate2 - var dotNetCert = new SystemX509.X509Certificate2(bcCert.GetEncoded()); - - // Use the new utility method - byte[] encoded = DotNetUtilities.GetSubjectPublicKeyInfoDer(dotNetCert); - - // Verify logic: RSA encoded SubjectPublicKeyInfo must have the correct structure - string hexEncoded = Hex.ToHexString(encoded).ToLowerInvariant(); - string expectedOidAndNull = "06092a864886f70d0101010500"; - - Assert.IsTrue(hexEncoded.Contains(expectedOidAndNull), - "DotNetUtilities.GetSubjectPublicKeyInfoDer failed to produce correct RSA encoding with NULL parameters."); -#endif - } - - [Test] - public void TestSubjectPublicKeyInfoFactoryConsistency() - { - // Verify that SubjectPublicKeyInfoFactory produces consistent results for RSA - RsaKeyPairGenerator pGen = new RsaKeyPairGenerator(); - pGen.Init(new KeyGenerationParameters(new SecureRandom(), 1024)); - AsymmetricCipherKeyPair pair = pGen.GenerateKeyPair(); - - SubjectPublicKeyInfo info = SubjectPublicKeyInfoFactory.CreateSubjectPublicKeyInfo(pair.Public); - AlgorithmIdentifier algId = info.AlgorithmID; - - Assert.AreEqual(PkcsObjectIdentifiers.RsaEncryption, algId.Algorithm); - Assert.IsInstanceOf(algId.Parameters, "RSA AlgorithmIdentifier parameters should be DerNull."); - } - } -} diff --git a/crypto/test/src/security/test/TestDotNetUtil.cs b/crypto/test/src/security/test/TestDotNetUtil.cs index b730fdfb2..2f54adee6 100644 --- a/crypto/test/src/security/test/TestDotNetUtil.cs +++ b/crypto/test/src/security/test/TestDotNetUtil.cs @@ -11,6 +11,7 @@ using Org.BouncyCastle.Asn1; using Org.BouncyCastle.Asn1.X509; using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Crypto.Generators; using Org.BouncyCastle.Crypto.Operators; using Org.BouncyCastle.Crypto.Parameters; using Org.BouncyCastle.Math; @@ -73,6 +74,38 @@ public void TestRsaInterop() } } +#if NET6_0_OR_GREATER + [Test] + public void TestGetSubjectPublicKeyInfoDer() + { + RsaKeyPairGenerator pGen = new RsaKeyPairGenerator(); + pGen.Init(new KeyGenerationParameters(new SecureRandom(), 1024)); + AsymmetricCipherKeyPair pair = pGen.GenerateKeyPair(); + + X509V3CertificateGenerator certGen = new X509V3CertificateGenerator(); + certGen.SetSerialNumber(BigInteger.One); + certGen.SetIssuerDN(new X509Name("CN=Test Issuer")); + certGen.SetSubjectDN(new X509Name("CN=Test Subject")); + certGen.SetNotBefore(DateTime.UtcNow.AddDays(-1)); + certGen.SetNotAfter(DateTime.UtcNow.AddDays(1)); + certGen.SetPublicKey(pair.Public); + + ISignatureFactory signatureFactory = new Asn1SignatureFactory("SHA256WithRSA", pair.Private); + X509Certificate bcCert = certGen.Generate(signatureFactory); + + var dotNetCert = (SystemX509.X509Certificate2)DotNetUtilities.ToX509Certificate(bcCert); + + byte[] encoded = DotNetUtilities.GetSubjectPublicKeyInfoDer(dotNetCert); + + // OID for rsaEncryption (06 09 2a 86 48 86 f7 0d 01 01 01) followed by NULL (05 00) + string hexEncoded = Hex.ToHexString(encoded).ToLowerInvariant(); + string expectedOidAndNull = "06092a864886f70d0101010500"; + + Assert.IsTrue(hexEncoded.Contains(expectedOidAndNull), + "GetSubjectPublicKeyInfoDer failed to produce correct RSA encoding with NULL parameters."); + } +#endif + [Test] public void TestX509CertificateConversion() { diff --git a/crypto/test/src/security/test/TestEncodings.cs b/crypto/test/src/security/test/TestEncodings.cs index 330054e84..12d16220f 100644 --- a/crypto/test/src/security/test/TestEncodings.cs +++ b/crypto/test/src/security/test/TestEncodings.cs @@ -2,9 +2,11 @@ using NUnit.Framework; +using Org.BouncyCastle.Asn1; using Org.BouncyCastle.Asn1.Pkcs; using Org.BouncyCastle.Asn1.X509; using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Crypto.Generators; using Org.BouncyCastle.Crypto.Parameters; using Org.BouncyCastle.Math; using Org.BouncyCastle.Math.EC; @@ -176,6 +178,40 @@ public void TestDSA() } + [Test] + public void TestRsaPublicKeyInfoEncodingHasNullParameters() + { + RsaKeyPairGenerator pGen = new RsaKeyPairGenerator(); + pGen.Init(new KeyGenerationParameters(new SecureRandom(), 1024)); + AsymmetricCipherKeyPair pair = pGen.GenerateKeyPair(); + RsaKeyParameters pubKey = (RsaKeyParameters)pair.Public; + + SubjectPublicKeyInfo info = SubjectPublicKeyInfoFactory.CreateSubjectPublicKeyInfo(pubKey); + byte[] encoded = info.GetEncoded(Asn1Encodable.Der); + + // RFC 8017 / PKCS#1 v2.2: AlgorithmIdentifier for rsaEncryption (1.2.840.113549.1.1.1) MUST include + // DER NULL (05 00) in its parameters field. + string hexEncoded = Hex.ToHexString(encoded).ToLowerInvariant(); + string expectedSequence = "06092a864886f70d0101010500"; + + Assert.IsTrue(hexEncoded.Contains(expectedSequence), + "RSA AlgorithmIdentifier in SubjectPublicKeyInfo missing mandatory NULL parameters (05 00)."); + } + + [Test] + public void TestSubjectPublicKeyInfoFactoryRsaConsistency() + { + RsaKeyPairGenerator pGen = new RsaKeyPairGenerator(); + pGen.Init(new KeyGenerationParameters(new SecureRandom(), 1024)); + AsymmetricCipherKeyPair pair = pGen.GenerateKeyPair(); + + SubjectPublicKeyInfo info = SubjectPublicKeyInfoFactory.CreateSubjectPublicKeyInfo(pair.Public); + AlgorithmIdentifier algId = info.AlgorithmID; + + Assert.AreEqual(PkcsObjectIdentifiers.RsaEncryption, algId.Algorithm); + Assert.IsInstanceOf(algId.Parameters, "RSA AlgorithmIdentifier parameters should be DerNull."); + } + [Test] public void TestGost2012() {