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..b898b6160 100644 --- a/crypto/src/security/DotNetUtilities.cs +++ b/crypto/src/security/DotNetUtilities.cs @@ -54,17 +54,76 @@ 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 (an X.509 ASN.1 type used for public keys) from a .NET + /// . + /// + /// The .NET certificate. + /// A object. + /// 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 + /// . + /// + /// The .NET certificate. + /// A byte array containing the DER-encoded public key info. + /// 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 + } + + /// + /// 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 +135,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 +168,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 +206,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 +245,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 +277,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 +300,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 +321,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 +364,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 +378,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 +391,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 +418,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 +437,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..7639b8077 100644 --- a/crypto/src/x509/SubjectPublicKeyInfoFactory.cs +++ b/crypto/src/x509/SubjectPublicKeyInfoFactory.cs @@ -17,8 +17,13 @@ namespace Org.BouncyCastle.X509 { /// - /// A factory to produce Public Key Info Objects. + /// 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. + /// public static class SubjectPublicKeyInfoFactory { private static readonly HashSet cryptoProOids = new HashSet @@ -31,11 +36,21 @@ 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 DER-encoded SubjectPublicKeyInfo 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/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() {