Creating Netsuite Certificate X509 using C# - c#

I am trying to generate a certificate using Bouncy Castle. When I load it into Netsuite it says certificate parsing error.
The certificate must meet the following requirements:
The public part of the certificate must be in x.509 format with a file extension of .cer, .pem, or .crt.
The length of the RSA key must be 3072 bits, or 4096 bits. The length of EC key must 256 bits, 384 bits, or 521 bits.
The maximum time period that a certificate can be valid is two years. If the certificate is valid for a longer time period, the system automatically shortens the time period to two years.
One certificate can only be used for one combination of integration record, role, and entity. If you want to use the same integration record for multiple entities or roles, you must use a different certificate for each unique combination.
The following example shows how to create a valid certificate using OpenSSL:
openssl req -x509 -newkey rsa:4096 -sha256 -keyout auth-key.pem -out auth-cert.pem -nodes -days 730.
Using the code below
public async Task GenerateX509Certificate()
{
int TenantId = (int)_session.TenantId;
var IntegrationData = await _appIntegrationRepository.FirstOrDefaultAsync(c => c.TenantId == TenantId);
RsaKeyPairGenerator g = new RsaKeyPairGenerator();
g.Init(new KeyGenerationParameters(new SecureRandom(), 3072));
AsymmetricCipherKeyPair pair = g.GenerateKeyPair();
PrivateKeyInfo privateKeyInfo = PrivateKeyInfoFactory.CreatePrivateKeyInfo(pair.Private);
byte[] serializedPrivateBytes = privateKeyInfo.ToAsn1Object().GetDerEncoded();
string serializedPrivate = Convert.ToBase64String(serializedPrivateBytes);
SubjectPublicKeyInfo publicKeyInfo = SubjectPublicKeyInfoFactory.CreateSubjectPublicKeyInfo(pair.Public);
byte[] serializedPublicBytes = publicKeyInfo.ToAsn1Object().GetDerEncoded();
string serializedPublic = Convert.ToBase64String(serializedPublicBytes);
// Distribute PEMs.
IntegrationData.PublicKey = serializedPublic;
IntegrationData.PrivateKey = serializedPrivate;
var key = GenerateX509Certificate("company","Los Angeles");
var outputkey = Convert.ToBase64String(key.GetRSAPrivateKey().ExportRSAPrivateKey());
TextWriter textWriter = new StringWriter();
PemWriter pemWriter = new PemWriter(textWriter);
pemWriter.WriteObject(key);
pemWriter.Writer.Flush();
string privateKey = textWriter.ToString();
await _appIntegrationRepository.UpdateAsync(IntegrationData);
}
public static X509Certificate2 GenerateX509Certificate(string commonName, string locality)
{
var keyPair = GetAsymmetricCipherKeyPair();
var certificateGenerator = GetX509V3CertificateGenerator(keyPair, commonName, locality);
var bouncyCastleCertificate = GenerateBouncyCastleCertificate(
keyPair, certificateGenerator);
return GenerateX509CertificateWithPrivateKey(
keyPair, bouncyCastleCertificate);
}
private static AsymmetricCipherKeyPair GetAsymmetricCipherKeyPair()
{
var keyPairGen = new RsaKeyPairGenerator();
var keyParams = new KeyGenerationParameters(
new SecureRandom(new CryptoApiRandomGenerator()), 4096);
keyPairGen.Init(keyParams);
var keyPair = keyPairGen.GenerateKeyPair();
return keyPair;
}
private static X509V3CertificateGenerator GetX509V3CertificateGenerator(
AsymmetricCipherKeyPair keyPair, string commonName, string locality)
{
IDictionary attrs = new Hashtable();
attrs[X509Name.CN] = commonName;
attrs[X509Name.L] = locality;
IList ord = new ArrayList();
ord.Add(X509Name.CN);
ord.Add(X509Name.L);
var gen = new X509V3CertificateGenerator();
gen.SetSerialNumber(BigInteger.ProbablePrime(120, new Random()));
gen.SetNotAfter(DateTime.Now.AddDays(1));
gen.SetNotBefore(DateTime.Now.AddDays(-1));
gen.SetPublicKey(keyPair.Public);
gen.AddExtension(X509Extensions.BasicConstraints, true, new BasicConstraints(false));
gen.SetSubjectDN(new X509Name(ord, attrs));
gen.SetIssuerDN(new X509Name(ord, attrs));
var ski = new SubjectKeyIdentifier(
SubjectPublicKeyInfoFactory.CreateSubjectPublicKeyInfo(keyPair.Public));
gen.AddExtension(X509Extensions.SubjectKeyIdentifier, false, ski);
var keyUsage = new KeyUsage(KeyUsage.DigitalSignature | KeyUsage.KeyCertSign);
gen.AddExtension(X509Extensions.KeyUsage, true, keyUsage);
return gen;
}
private static X509Certificate GenerateBouncyCastleCertificate(
AsymmetricCipherKeyPair keyPair, X509V3CertificateGenerator gen)
{
ISignatureFactory sigFact = new Asn1SignatureFactory(
"SHA256WithRSA", keyPair.Private);
var bcCert = gen.Generate(sigFact);
return bcCert;
}
private static X509Certificate2 GenerateX509CertificateWithPrivateKey(
AsymmetricCipherKeyPair keyPair,
X509Certificate bcCert)
{
var privateKeyInfo = PrivateKeyInfoFactory.CreatePrivateKeyInfo(keyPair.Private);
var asn1Seq = (Asn1Sequence)Asn1Object.FromByteArray(
privateKeyInfo.ParsePrivateKey().GetDerEncoded());
var rsaPrivateKeyStruct = RsaPrivateKeyStructure.GetInstance(asn1Seq);
var rsa = DotNetUtilities.ToRSA(rsaPrivateKeyStruct);
var x509Cert = new X509Certificate2(bcCert.GetEncoded());
return x509Cert.CopyWithPrivateKey(rsa);
}

Related

Converting from a Public Microsoft.IdentityModel.Tokens.JsonWebKey.JsonWebKey to RSAParameters (Public Key)

Essentially, I'm looking to convert a Public Microsoft.IdentityModel.Tokens.JsonWebKey.JsonWebKey to RSAParameters to then use in an RSA Instance. After this, i'm creating an Azure KeyVault JsonWebKey so I can import this key into my vault. I currently tried this, however have not gotten it to work. Any recommendations/shortcuts?
var jwk = new JsonWebKey(someStr); // IdentityModel.Tokens...
var rsaParams = new RSAParameters
{
Modulus = WebEncoders.Base64UrlDecode(jwk.N),
Exponent = WebEncoders.Base64UrlDecode(jwk.E)
};
var rsa = RSA.Create(rsaParams);
var key = new JsonWebKey(rsa); // Azure.Security.KeyVault.Keys
....
var kvKey = keyClient.ImportKey(keyName, key); // keyClient = KeyClient class
The error I am receiving from this request is:
RSA key is not valid - cannot instantiate crypto service
Try adding other rsa parameters and then importing into an RSACryptoServiceProvider. Then you can recover the SecurityKey.
The code below is an example of how to recover a SecurityKey from a previous RSASecurityKey stored in a JsonWebKey
using RSACryptoServiceProvider provider = new RSACryptoServiceProvider(2048);
JsonWebKey key = JsonConvert.DeserializeObject<JsonWebKey>(jsonwebkeystringcontent);
RSAParameters rsaParameters = new()
{
Modulus = WebEncoders.Base64UrlDecode(key.N),
Exponent = WebEncoders.Base64UrlDecode(key.E),
D = WebEncoders.Base64UrlDecode(key.D),
DP = WebEncoders.Base64UrlDecode(key.DP),
DQ = WebEncoders.Base64UrlDecode(key.DQ),
P = WebEncoders.Base64UrlDecode(key.P),
Q = WebEncoders.Base64UrlDecode(key.Q),
InverseQ = WebEncoders.Base64UrlDecode(key.QI)
};
provider.ImportParameters(rsaParameters);
SecurityKey Key = new RsaSecurityKey(provider.ExportParameters(true));

Associating an X509Certificate2 certificate with a private key in .NET

I'm trying to create an X509Certificate2 object by using this code: https://stackoverflow.com/a/9250034/5589417
How can I get the private key that corresponds to the certificate's public key?
I have these methods for encryption and decryption:
public static byte[] Encrypt(byte[] plainBytes, X509Certificate2 cert)
{
RSACryptoServiceProvider publicKey = (RSACryptoServiceProvider)cert.PublicKey.Key;
byte[] encryptedBytes = publicKey.Encrypt(plainBytes, false);
return encryptedBytes;
}
public static byte[] Decrypt(byte[] encryptedBytes, X509Certificate2 cert)
{
RSACryptoServiceProvider privateKey = (RSACryptoServiceProvider)cert.PrivateKey;
byte[] decryptedBytes = privateKey.Decrypt(encryptedBytes, false);
return decryptedBytes;
}
When I use the Decrypt method I get a NullRefereneceException for privateKey.
We have to setup manually PrivateKey property for certificate instance. I also updated code from old answer you linked and removed using of obsolete methods:
static void Main(string[] args)
{
var cert = GenerateCertificate("localhost");
byte[] ciphertext = Encrypt(Encoding.ASCII.GetBytes("Hello world!"), cert);
byte[] plaintext = Decrypt(ciphertext, cert);
Console.WriteLine(Encoding.ASCII.GetString(plaintext));
}
static X509Certificate2 GenerateCertificate(string certName)
{
var secureRandom = new SecureRandom(new CryptoApiRandomGenerator());
var keypairgen = new RsaKeyPairGenerator();
// RSA key size = 1024 bits
keypairgen.Init(new KeyGenerationParameters(secureRandom, 1024));
var keypair = keypairgen.GenerateKeyPair();
var gen = new X509V3CertificateGenerator();
// we will use SHA256 signature
var signatureFactory = new Asn1SignatureFactory("SHA256WITHRSA", keypair.Private, secureRandom);
var CN = new X509Name("CN=" + certName);
var SN = BigInteger.ProbablePrime(120, new Random());
gen.SetSerialNumber(SN);
gen.SetSubjectDN(CN);
gen.SetIssuerDN(CN);
gen.SetNotAfter(DateTime.MaxValue);
gen.SetNotBefore(DateTime.Now.Subtract(new TimeSpan(7, 0, 0, 0)));
gen.SetPublicKey(keypair.Public);
var newCert = gen.Generate(signatureFactory);
var x509cert = new X509Certificate2(DotNetUtilities.ToX509Certificate(newCert));
var rsa = RSA.Create();
var publicKey = (RsaKeyParameters)keypair.Public;
var privateKey = (RsaPrivateCrtKeyParameters)keypair.Private;
var parameters = new RSAParameters
{
Modulus = publicKey.Modulus.ToByteArrayUnsigned(),
Exponent = publicKey.Exponent.ToByteArrayUnsigned(),
P = privateKey.P.ToByteArrayUnsigned(),
Q = privateKey.Q.ToByteArrayUnsigned(),
DP = privateKey.DP.ToByteArrayUnsigned(),
DQ = privateKey.DQ.ToByteArrayUnsigned(),
InverseQ = privateKey.QInv.ToByteArrayUnsigned(),
D = privateKey.Exponent.ToByteArrayUnsigned(),
};
rsa.ImportParameters(parameters);
// at this point X509Certificate2 will check if PrivateKey matches PublicKey
x509cert.PrivateKey = rsa;
return x509cert;
}
public static byte[] Encrypt(byte[] plainBytes, X509Certificate2 cert)
{
RSACryptoServiceProvider publicKey = (RSACryptoServiceProvider)cert.PublicKey.Key;
byte[] encryptedBytes = publicKey.Encrypt(plainBytes, false);
return encryptedBytes;
}
public static byte[] Decrypt(byte[] encryptedBytes, X509Certificate2 cert)
{
RSACryptoServiceProvider privateKey = (RSACryptoServiceProvider)cert.PrivateKey;
byte[] decryptedBytes = privateKey.Decrypt(encryptedBytes, false);
return decryptedBytes;
}

Generate key/Encryption/Decryption for RSACryptoProvider and BouncyCastle

Key generated through RSACryptoProvider is work for BouncyCastle Encryption (using publickey) / Decryption (using privatekey) ?
using (RSACryptoServiceProvider rsa = new RSACryptoServiceProvider())
{
var pu = rsa.ToXmlString(false);
var pr = rsa.ToXmlString(true);
}
Also, how to generate key using BouncyCastle ?
Answer to first question, yes, RSA is a standard and it doesn't depends on the libraries used.
Second, try this:
public static void GetRsaKeyPair(out string privateXml, out string publicXml)
{
CryptoApiRandomGenerator randomGenerator = new CryptoApiRandomGenerator();
SecureRandom secureRandom = new SecureRandom(randomGenerator);
var keyGenerationParameters = new KeyGenerationParameters(secureRandom, 1024);
var rsaKeyPairGenerator = new RsaKeyPairGenerator();
rsaKeyPairGenerator.Init(keyGenerationParameters);
AsymmetricCipherKeyPair rsaKeyPair = rsaKeyPairGenerator.GenerateKeyPair();
var privateRsaParameters = DotNetUtilities.ToRSAParameters((RsaPrivateCrtKeyParameters)rsaKeyPair.Private);
using (RSACryptoServiceProvider rsaProvider = new RSACryptoServiceProvider())
{
rsaProvider.ImportParameters(privateRsaParameters);
privateXml = rsaProvider.ToXmlString(true);
publicXml = rsaProvider.ToXmlString(false);
}
}

Diffie Hellman Key Exchange using ECDSA x509 certificates

I am trying to perform a Diffie-Hellman key exchange using 2 ECDSA x509 certificates.
Here is the method where I extract the keys from the certificates for computation of the derived key.
private byte[] GetDerivedKey(X509Certificate2 publicCertificate, X509Certificate2 privateCertificate)
{
byte[] derivedKey;
using (var privateKey = privateCertificate.GetECDsaPrivateKey())
using (var publicKey = publicCertificate.GetECDsaPublicKey())
{
var privateParams = privateKey.ExportParameters(true); //This line is failing
var publicParams = publicKey.ExportParameters(false);
using (var privateCng = ECDiffieHellmanCng.Create(privateParams))
using (var publicCng = ECDiffieHellmanCng.Create(publicParams))
{
derivedKey = privateCng.DeriveKeyMaterial(publicCng.PublicKey);
}
}
return derivedKey;
}
I've commented on the line that is failing privateKey.ExportParameters(true) with the error:
System.Security.Cryptography.CryptographicException : The requested operation is not supported.
at System.Security.Cryptography.NCryptNative.ExportKey(SafeNCryptKeyHandle key, String format)
at System.Security.Cryptography.CngKey.Export(CngKeyBlobFormat format)
at System.Security.Cryptography.ECCng.ExportParameters(CngKey key, Boolean includePrivateParameters, ECParameters& ecparams)
at System.Security.Cryptography.ECDsaCng.ExportParameters(Boolean includePrivateParameters)
Because this is a self signed certificate that I am generating, I assume I am doing something wrong.
I first create a root CA certificate and pass in the private key to sign my certificate.
private X509Certificate2 CreateECSDACertificate(string certificateName,
string issuerCertificateName,
TimeSpan lifetime,
AsymmetricKeyParameter issuerPrivateKey,
string certificateFriendlyName = null)
{
// Generating Random Numbers
var randomGenerator = new CryptoApiRandomGenerator();
var random = new SecureRandom(randomGenerator);
var signatureFactory = new Asn1SignatureFactory("SHA256WithECDSA", issuerPrivateKey, random);
// The Certificate Generator
var certificateGenerator = new X509V3CertificateGenerator();
// Serial Number
var serialNumber = BigIntegers.CreateRandomInRange(BigInteger.One, BigInteger.ValueOf(Int64.MaxValue), random);
certificateGenerator.SetSerialNumber(serialNumber);
// Issuer and Subject Name
var subjectDistinguishedName = new X509Name($"CN={certificateName}");
var issuerDistinguishedName = new X509Name($"CN={issuerCertificateName}");
certificateGenerator.SetSubjectDN(subjectDistinguishedName);
certificateGenerator.SetIssuerDN(issuerDistinguishedName);
// Valid For
var notBefore = DateTime.UtcNow.Date;
var notAfter = notBefore.Add(lifetime);
certificateGenerator.SetNotBefore(notBefore);
certificateGenerator.SetNotAfter(notAfter);
//key generation
var keyGenerationParameters = new KeyGenerationParameters(random, _keyStrength);
var keyPairGenerator = new ECKeyPairGenerator();
keyPairGenerator.Init(keyGenerationParameters);
var subjectKeyPair = keyPairGenerator.GenerateKeyPair();
certificateGenerator.SetPublicKey(subjectKeyPair.Public);
var certificate = certificateGenerator.Generate(signatureFactory);
var store = new Pkcs12Store();
var certificateEntry = new X509CertificateEntry(certificate);
store.SetCertificateEntry(certificateName, certificateEntry);
store.SetKeyEntry(certificateName, new AsymmetricKeyEntry(subjectKeyPair.Private), new[] { certificateEntry });
X509Certificate2 x509;
using (var pfxStream = new MemoryStream())
{
store.Save(pfxStream, null, new SecureRandom());
pfxStream.Seek(0, SeekOrigin.Begin);
x509 = new X509Certificate2(pfxStream.ToArray());
}
x509.FriendlyName = certificateFriendlyName;
return x509;
}
The .HasPrivateKey() method returns true, which I've read can return a false positive.
When I add my certificates to the store, I can verify the cert chain.
[Test]
public void CreateSelfSignedCertificate_AfterAddingToStore_CanBuildChain()
{
var result = _target.CreateSelfSignedCertificate(_subject, _issuer, TimeSpan.FromDays(356), _certificateFriendlyName, _issuerFriendlyName);
_store.TryAddCertificateToStore(result.CertificateAuthority, _caStoreName, _location);
_store.TryAddCertificateToStore(result.Certificate, _certStoreName, _location);
var chain = new X509Chain
{
ChainPolicy =
{
RevocationMode = X509RevocationMode.NoCheck
}
};
var chainBuilt = chain.Build(result.Certificate);
if (!chainBuilt)
{
foreach (var status in chain.ChainStatus)
{
Assert.Warn(string.Format("Chain error: {0} {1}", status.Status, status.StatusInformation));
}
}
Assert.IsTrue(chainBuilt, "Chain");
}
I thought at first that maybe the private cert had to come from the cert store, so I imported it and then pulled it back out, but I get the same error, which is another reason I believe I'm not doing something quite right.
EDIT:
I have another class generating RSA x509's using the same code for putting the private key into the certificate. It allows me to export the RSA private key.
The variable _keyStrength is 384 and my signature factory is using "SHA256withECDSA". I have also tried using "SHA384withECDSA" but I get the same error.
OK. It's a blind shot but after looking at your code I noticed two things:
When you create PFX you set null password. But when you load the PFX into X509Certificate2 class you are using wrong constructor. You should use one with a password parameter and give a null into it
When you load PFX into X509Certificate2 class you do not specify, if the private key should be exportable. I think that this is the reason why privateKey.ExportParameters(true) gives you an exception. You should use this constructor and specify null as password
Made it working
I thought it was a bug. It's possible that it is. We clearly stated in X509Constructor that the private key should be exportable. I used X509KeyStorageFlags.EphemeralKeySet | X509KeyStorageFlags.Exportable flags too. But when I looked at the CngKey it had ExportPolicy set to AllowExport but not AllowPlaintextExport.
It was exportable in some way. privateKey.Key.Export(CngKeyBlobFormat.OpaqueTransportBlob) worked. But privateKey.ExportParameters(true) did not.
I've searched for a solution how to change ExportPolicy of CngKey. I found this SO question that helped me to change it. After that the ExportParameters worked.
The fixed version of your GetDerivedKey method is
private byte[] GetDerivedKey(X509Certificate2 publicCertificate, X509Certificate2 privateCertificate)
{
byte[] derivedKey;
using (var privateKey = privateCertificate.GetECDsaPrivateKey())
using (var publicKey = privateCertificate.GetECDsaPublicKey())
{
var myPrivateKeyToMessWith = privateKey as ECDsaCng;
// start - taken from https://stackoverflow.com/q/48542233/3245057
// make private key exportable:
byte[] bytes = BitConverter.GetBytes((int)(CngExportPolicies.AllowExport | CngExportPolicies.AllowPlaintextExport));
CngProperty pty = new CngProperty(NCryptExportPolicyProperty, bytes, CngPropertyOptions.Persist);
myPrivateKeyToMessWith.Key.SetProperty(pty);
// end - taken from https://stackoverflow.com/q/48542233/3245057
var privateParams = myPrivateKeyToMessWith.ExportParameters(true); //This line is NOT failing anymore
var publicParams = publicKey.ExportParameters(false);
using (var privateCng = ECDiffieHellmanCng.Create(privateParams))
using (var publicCng = ECDiffieHellmanCng.Create(publicParams))
{
derivedKey = privateCng.DeriveKeyMaterial(publicCng.PublicKey);
}
}
return derivedKey;
}
I started using the solution #pepo posted which lead me to discover 'GetECDsaPrivateKey' does not return an ECDsa object but an ECDsaCng. I simplified the key derivation to this.
byte[] derivedKey;
using (var privateKey = (ECDsaCng)certificate.GetECDsaPrivateKey())
using (var publicKey = (ECDsaCng)certificate.GetECDsaPublicKey())
{
var publicParams = publicKey.ExportParameters(false);
using (var publicCng = ECDiffieHellmanCng.Create(publicParams))
using (var diffieHellman = new ECDiffieHellmanCng(privateKey.Key))
{
derivedKey = diffieHellman.DeriveKeyMaterial(publicCng.PublicKey);
}
}
return derivedKey;

Create a X509Certificate2 from RSACryptoServiceProvider fails with Cannot find the requested object

Sample code:
CspParameters cspParameters = new CspParameters();
cspParameters.ProviderType = 1; // PROV_RSA_FULL
// Create the crypto service provider, generating a new
// key.
mRsaCSP = new RSACryptoServiceProvider(mDefaultKeyLength, cspParameters);
mRsaCSP.PersistKeyInCsp = true;
RSAParameters privateKey = mRsaCSP.ExportParameters(true);
byte[] rsaBytes = mRsaCSP.ExportCspBlob(true);
try
{
X509Certificate2 cert = new X509Certificate2(rsaBytes);
mKeyDataPfx = Convert.ToBase64String(cert.Export(X509ContentType.Pkcs12, password));
}
catch (Exception ce)
{
string error = ce.Message;
}
Here is my solution, using the BouncyCastle library.
// create the RSA key from an XML string
RSACryptoServiceProvider key = new RSACryptoServiceProvider();
key.FromXmlString(keyTextBox.Text);
// convert to BouncyCastle key object
var keypair = DotNetUtilities.GetRsaKeyPair(key);
var gen = new X509V3CertificateGenerator();
string certName = Path.GetFileNameWithoutExtension(fileName);
var name = new X509Name("CN=" + certName);
var serial = BigInteger.ProbablePrime(120, new Random());
gen.SetSerialNumber(serial);
gen.SetSubjectDN(name);
gen.SetIssuerDN(name);
gen.SetNotAfter(DateTime.Now.AddYears(10));
gen.SetNotBefore(DateTime.Now);
gen.SetSignatureAlgorithm("MD5WithRSA");
gen.SetPublicKey(keypair.Public);
// generate the certificate
var newCert = gen.Generate(keypair.Private);
// convert back to .NET certificate
var cert = DotNetUtilities.ToX509Certificate(newCert);
// export as byte array
byte[] certData = cert.Export(X509ContentType.Pfx);
File.WriteAllBytes(fileName, certData);

Categories

Resources