here in Italy, we will need to digitally sign all invoices since January 2019.
I found a code that works well with sha-1, but I need to use sha256 as standard.
The code below, after successfully detect USB key, and ask me for the certificate to use try to sign "NomeFile" file After and output in "NomeFile".p7m, when the line
signedCms.ComputeSignature(signer,false);
runs, it happens:
1- if use sha-1 it asks me for the PIN and document is successfully created.
2- if use sha-256 don't ask for PIN and gives me Unknown error -1073741275
I read a lot of posts that are all old (2011-2014). Other people have the same problem and seem that Microsoft has some bug using sha256.
Now we are at end of 2018 and I tried this code in .net 4, 4.6.1 and 4.7.2 but the error is the same.
Somebody can tell me if Microsoft corrects the problem with sha256 and what could be this strange error? (-1073741275)
Error Stack
public String FirmaFile(String NomeFile, DateTime DataFirma, X509Certificate2 cert, out string RisFirma)
{
String NomeFirma = NomeFile + ".p7m";
RisFirma = "";
try
{
// content contiene il file da firmare
ContentInfo content = new ContentInfo((File.ReadAllBytes(NomeFile)));
// assegniamo content ad un oggetto di tipo SignedCms
SignedCms signedCms = new SignedCms(SubjectIdentifierType.IssuerAndSerialNumber, content, false);
// si instanzia un oggetto CmsSigner che espone i metodi di firma.
CmsSigner signer = new CmsSigner(cert);
signer.IncludeOption = X509IncludeOption.EndCertOnly;
//signer.DigestAlgorithm = new Oid("2.16.840.1.101.3.4.2.1");
signer.DigestAlgorithm = new Oid("SHA256");
signer.SignedAttributes.Add(new Pkcs9SigningTime(DataFirma));
try
{
// Viene calcolata la firma del file (in formato PKCS7)
signedCms.ComputeSignature(signer,false);
}
catch (CryptographicException CEx)
{
RisFirma = "Errore: " + CEx.Message + " Stack: " + CEx.StackTrace;
return RisFirma;
}
// si pone il file firmato in un array.
byte[] signature = signedCms.Encode();
File.WriteAllBytes(NomeFirma, signature);
RisFirma = "true";
}
catch (Exception Ex)
{
RisFirma = "Errore in FirmaFile: " + Ex.Message + " Stack: " + Ex.StackTrace;
}
return RisFirma;
}
NB: I tried 2 version of OID
signer.DigestAlgorithm = new Oid("2.16.840.1.101.3.4.2.1");
signer.DigestAlgorithm = new Oid("SHA256");
All 2 give the same error.
I USE an INFOCERT USB KEY with driver bit4id (https://www.bit4id.com/it/4identity/) that is contained in USB Drive.
The error and symptom seem to indicate that the CSP (Cryptographic Service Provider) which is doing the signing operation doesn't support SHA-2. If it's working in BouncyCastle then they are seemingly exporting the private key and re-importing it into their software provider.
In .NET 4.7.2 you could try the following:
...
try
{
// Viene calcolata la firma del file (in formato PKCS7)
signedCms.ComputeSignature(signer,false);
}
catch (CryptographicException CEx)
{
try
{
// Try re-importing the private key into a better CSP:
using (RSA tmpRsa = RSA.Create())
{
tmpRsa.ImportParameters(cert.GetRSAPrivateKey().ExportParameters(true));
using (X509Certificate2 tmpCertNoKey = new X509Certificate2(cert.RawData))
using (X509Certificate2 tmpCert = tmpCertNoKey.CopyWithPrivateKey(tmpRsa))
{
signer.Certificate = tmpCert;
signedCms.ComputeSignature(signer,false);
}
}
}
catch (CryptographicException)
{
// This is the original exception, not the inner one.
RisFirma = "Errore: " + CEx.Message + " Stack: " + CEx.StackTrace;
return RisFirma;
}
}
If the certificate is actually being loaded from a PFX file on the USB device, then the problem is that the PFX specifies to use an older software CSP which predated SHA-2. Getting the PFX regenerated to use the newest RSA CSP would also solve the problem.
use this:
private string podpisz(X509Certificate2 cert, string toSign)
{
string output = "";
try
{
RSACryptoServiceProvider csp = null;
csp = (RSACryptoServiceProvider)cert.PrivateKey;
// Hash the data
SHA256Managed sha256 = new SHA256Managed();
UnicodeEncoding encoding = new UnicodeEncoding();
byte[] data = Encoding.Default.GetBytes(toSign);
byte[] hash = sha256.ComputeHash(data);
// Sign the hash
byte[] wynBin = csp.SignHash(hash, CryptoConfig.MapNameToOID("SHA256"));
output = Convert.ToBase64String(wynBin);
}
catch (Exception)
{
}
return output;
}
I found this in internet, I try it and incredibly it worked!
Anyway the solution is bit more complex.
You have to use BouncyCastle (https://www.bouncycastle.org/) library.
But not the version avaiable, but a version that was modified by on user on another forum.
The link to the bouncy castle library modified is:
http://www.mediafire.com/download/uc63d1hepqyuhee/bccrypto-net-1.7-src-ext_with_CADES-BES.zip
You have to use crypto.dll library found in bin\release and reference it in your project.
Theese are all using I have now, probably not all are required for this specified case:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Security.Cryptography;
using System.Security.Cryptography.Xml;
using System.Security.Cryptography.Pkcs;
using System.Security.Cryptography.X509Certificates;
using System.Xml;
using System.IO;
using System.Collections;
using CryptoUpgNet.NonExportablePK;
using Org.BouncyCastle.Asn1;
using Org.BouncyCastle.Asn1.Ess;
using Org.BouncyCastle.Cms;
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Pkcs;
using Org.BouncyCastle.Security;
This is the function:
public byte[] FirmaFileBouncy(String NomeFile, X509Certificate2 cert, ref string RisFirma)
{
String NomeFirma = NomeFile + ".p7m";
try
{
SHA256Managed hashSha256 = new SHA256Managed();
byte[] certHash = hashSha256.ComputeHash(cert.RawData);
EssCertIDv2 essCert1 = new EssCertIDv2(new Org.BouncyCastle.Asn1.X509.AlgorithmIdentifier("2.16.840.1.101.3.4.2.1"), certHash);
SigningCertificateV2 scv2 = new SigningCertificateV2(new EssCertIDv2[] { essCert1 });
Org.BouncyCastle.Asn1.Cms.Attribute CertHAttribute = new Org.BouncyCastle.Asn1.Cms.Attribute(Org.BouncyCastle.Asn1.Pkcs.PkcsObjectIdentifiers.IdAASigningCertificateV2, new DerSet(scv2));
Asn1EncodableVector v = new Asn1EncodableVector();
v.Add(CertHAttribute);
Org.BouncyCastle.Asn1.Cms.AttributeTable AT = new Org.BouncyCastle.Asn1.Cms.AttributeTable(v);
CmsSignedDataGenWithRsaCsp cms = new CmsSignedDataGenWithRsaCsp();
dynamic rsa = (RSACryptoServiceProvider)cert.PrivateKey;
Org.BouncyCastle.X509.X509Certificate certCopy = DotNetUtilities.FromX509Certificate(cert);
cms.MyAddSigner( rsa, certCopy, "1.2.840.113549.1.1.1", "2.16.840.1.101.3.4.2.1", AT, null);
ArrayList certList = new ArrayList();
certList.Add(certCopy);
Org.BouncyCastle.X509.Store.X509CollectionStoreParameters PP = new Org.BouncyCastle.X509.Store.X509CollectionStoreParameters(certList);
Org.BouncyCastle.X509.Store.IX509Store st1 = Org.BouncyCastle.X509.Store.X509StoreFactory.Create("CERTIFICATE/COLLECTION", PP);
cms.AddCertificates(st1);
//mi ricavo il file da firmare
FileInfo File__1 = new FileInfo(NomeFile);
CmsProcessableFile file__2 = new CmsProcessableFile(File__1);
CmsSignedData Firmato = cms.Generate(file__2, true);
byte[] Encoded = Firmato.GetEncoded();
File.WriteAllBytes(NomeFirma, Encoded);
RisFirma = "true";
return Encoded;
} catch (Exception ex) {
RisFirma = ex.ToString();
return null;
}
}
Edit: Using it repeatly with same certificate, it ask PIN only the first time. So is good to make multiple files at once with security standard active.
To Grzegorz:
The source file is 5k
The correct signed file (IT01234567890_FPA01_2.xml.p7m is 8k
The file saved with your routine adding a
File.WriteAllBytes("c:\\temp\\IT01234567890_FPA01.xml.p7m", wynBin);
after
byte[] wynBin = csp.SignHash(hash, CryptoConfig.MapNameToOID("SHA256"));
is only 1kb and is not reckognized by Dike.
Sign not reckognized
Difference between files
Related
i need to decrypt a pdf file using bouncy castle libreries.
I use c#.
I can encrypt a pdf file using the following code:
public static byte[] CmsEncrypt3DES(string InFilePath, string CertFilePath)
{
byte[] encoded;
try
{
byte[] numArray = File.ReadAllBytes(InFilePath);
X509Certificate x509Certificate =ESCmsEncrypt.LoadCertificate(CertFilePath);
CmsEnvelopedDataGenerator cmsEnvelopedDataGenerator = new CmsEnvelopedDataGenerator();
CmsProcessableByteArray cmsProcessableByteArray = new CmsProcessableByteArray(numArray);
cmsEnvelopedDataGenerator.AddKeyTransRecipient(x509Certificate);
encoded = cmsEnvelopedDataGenerator.Generate(cmsProcessableByteArray, PkcsObjectIdentifiers.DesEde3Cbc.Id).GetEncoded();
}
catch (Exception exception1)
{
Exception exception = exception1;
throw exception;
}
return encoded;
}
I import the certificate using this method:
public static X509Certificate LoadCertificate(string filename)
{
X509Certificate x509Certificate;
try
{
X509CertificateParser x509CertificateParser = new X509CertificateParser();
FileStream fileStream = new FileStream(filename, FileMode.Open);
X509Certificate x509Certificate1 = x509CertificateParser.ReadCertificate(fileStream);
fileStream.Close();
x509Certificate = x509Certificate1;
}
catch (Exception exception1)
{
Exception exception = exception1;
Log.LogError("ESCmsEncrypt.LoadCertificate", exception.Message, exception.ToString());
throw exception;
}
return x509Certificate;
}
This code work well but i' don't know how to decrypt this file using bouncy castle.
I've tried but without results.
Thanks
Valerio M.
EDIT
i've also used this code but it doesn't work:
public static void test()
{
X509Certificate2Collection scollection = new X509Certificate2Collection();
// Output which certificate will be used
Console.WriteLine("Using Certificate:");
string path_certificato = ConfigurationManager.AppSettings["certificate_file"].ToString();
X509Certificate2 cert = new X509Certificate2();
X509Certificate2 x509 = new X509Certificate2(File.ReadAllBytes(path_certificato));
Console.WriteLine("---------------------------------------------------------------------");
Console.WriteLine("1.\tFull DN: {0}", x509.Subject);
Console.WriteLine("\tThumbprint: {0}", x509.Thumbprint);
Console.WriteLine("---------------------------------------------------------------------");
cert = x509;
scollection.Add(cert);
// Wait
Console.WriteLine("Press any key to continue...");
Console.ReadKey(true);
// Create data for encryption
string message = "THIS IS OUR SECRET MESSAGE";
byte[] data = System.Text.Encoding.ASCII.GetBytes(message);
// Encrypt
Console.WriteLine("Encrypting message...");
//ContentInfo contentInfo = new ContentInfo(data); // will use default ContentInfo Oid, which is "DATA"
// Explicitly use ContentInfo Oid 1.2.840.113549.1.7.1, "DATA", which is the default.
ContentInfo contentInfo = new ContentInfo(new System.Security.Cryptography.Oid("1.2.840.113549.1.7.1"), data);
// If using OID 1.2.840.113549.3.7 (the default one used if empty constructor is used) or 1.2.840.113549.1.9.16.3.6 everything works
// If using OID 2.16.840.1.101.3.4.1.42 (AES CBC) it breaks
AlgorithmIdentifier encryptionAlgorithm = new AlgorithmIdentifier(new System.Security.Cryptography.Oid("1.2.840.113549.3.7"));
EnvelopedCms envelopedCms = new EnvelopedCms(contentInfo); // this will use default encryption algorithm (3DES)
//EnvelopedCms envelopedCms = new EnvelopedCms(contentInfo, encryptionAlgorithm);
Console.WriteLine("Encyption Algorithm:" + envelopedCms.ContentEncryptionAlgorithm.Oid.FriendlyName);
Console.WriteLine("Encyption Algorithm:" + envelopedCms.ContentEncryptionAlgorithm.Oid.Value);
CmsRecipientCollection recipients = new CmsRecipientCollection(SubjectIdentifierType.IssuerAndSerialNumber, scollection);
/*Console.WriteLine("Receipientinfo count: " + encryptionEnvelopedCms.RecipientInfos.Count.ToString());
foreach (var i in encryptionEnvelopedCms.RecipientInfos)
{
Console.Write("RecipientInfo Encryption Oid: " + i.KeyEncryptionAlgorithm.Oid);
}
*/
envelopedCms.Encrypt(recipients);
byte[] encryptedData = envelopedCms.Encode();
Console.WriteLine("Message encrypted!");
EnvelopedCms envelopedCms_new = new EnvelopedCms();
envelopedCms_new.Decode(encryptedData);
envelopedCms_new.Decrypt(scollection);
byte[] decryptedData = envelopedCms_new.ContentInfo.Content;
}
"envelopedCms_new.Decode(encryptedData);"this part doesn't work
System.Security.Cryptography.CryptographicException: 'Impossibile trovare la proprietà o l'oggetto.
I am trying to sign the file with the certificate stored in the certificate storage. The certificate is stored in the Local Computer storage.
While calling of
signedCms.ComputeSignature(cmsSigner, false);
I get the next CryptographicException: "The buffer supplied to a function was too small"
UPDATE
Code example:
using System;
using System.Security.Cryptography.X509Certificates;
using System.Security.Cryptography;
using System.Text;
namespace ConsoleApp
{
class Program
{
static void Main(string[] args)
{
const string ENC_STR = "this is my message to encrypt цшер ÄÄÄ";
var oid = new System.Security.Cryptography.Oid("sha256");
X509Store store = new X509Store("MY", StoreLocation.LocalMachine);
store.Open(OpenFlags.ReadOnly);
X509Certificate2Collection collection = (X509Certificate2Collection)store.Certificates;
X509Certificate2Collection cers = store.Certificates.Find(X509FindType.FindBySubjectName, args[0], false);
if (cers.Count > 0)
{
Console.WriteLine(" {0} certificates are found", cers.Count);
var cer = cers[0];
Console.WriteLine("Certificate {0} is found", cer.Subject);
byte[] dataToSign = Encoding.ASCII.GetBytes(ENC_STR);
System.Security.Cryptography.Pkcs.ContentInfo contentInfo = new System.Security.Cryptography.Pkcs.ContentInfo(dataToSign);
System.Security.Cryptography.Pkcs.SignedCms signedCms = new System.Security.Cryptography.Pkcs.SignedCms(contentInfo, true);
System.Security.Cryptography.Pkcs.CmsSigner cmsSigner = new System.Security.Cryptography.Pkcs.CmsSigner(cer);
cmsSigner.IncludeOption = X509IncludeOption.None;
cmsSigner.DigestAlgorithm = oid;
try
{
signedCms.ComputeSignature(cmsSigner, false);
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
//return the signed message
byte[] signatureValue = signedCms.Encode();
Console.WriteLine("Signature is: {0} ", Encoding.ASCII.GetString(signatureValue));
}
else
{
Console.WriteLine("No certificates found");
}
store.Close();
}
}
}
Maybe this information will be helpful:
The certificate has "äåö" characters in the Common Name and
Organisation
The exception is not thrown if the hash algorithm is
SHA1. For sha256 (and other) exception is thrown.
If I port the
specific part of code to the .net core or .net standard the exception
is not thrown.
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;
Is it possible somehow to save or export CX509PrivateKey. The idea is that I create a CSR sent to CA get a Certificate and then... somehow I have to get the private key but no idea how, unfortunately nothing found on google.
My piece of code:
var objPrivateKey = new CX509PrivateKey();
objPrivateKey.Length = int.Parse(ConfigurationManager.AppSettings["objPrivateKeyLength"]);
objPrivateKey.KeySpec = X509KeySpec.XCN_AT_SIGNATURE;
objPrivateKey.KeyUsage = X509PrivateKeyUsageFlags.XCN_NCRYPT_ALLOW_ALL_USAGES;
objPrivateKey.MachineContext = false;
objPrivateKey.ExportPolicy = X509PrivateKeyExportFlags.XCN_NCRYPT_ALLOW_EXPORT_FLAG;
objPrivateKey.CspInformations = objCSPs;
objPrivateKey.Create();
Actually I have solved this task in this way. (Here is a bit more code, so you can understand what I was trying to do).
Here all the job is done.
/*
using CERTADMINLib;
using CERTCLILib;
using CertSevAPI.Core.Models;
using CertSrvAPI.Core;
using CertSrvAPI.Core.Models;
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.Prng;
using Org.BouncyCastle.Pkcs;
using Org.BouncyCastle.Security;
using System;
*/
var caService = new CAService();
// Create a certificate request.
// The private key is here.
var caRequest = caService.CertRequest(subjectDN);
// Submit the certificate request and get the response.
var caResponse = caService.SendCertRequest(caRequest.Request);
// If certificated is not issued return null.
if (!caService.IsIssued(caResponse.Disposition))
{
return null;
}
// Download the P7B file from CA.
var p7b = new WebClient().DownloadData(
_appSettings.CERT_SRV + "/CertSrv/CertNew.p7b?ReqID=" + caResponse.CertRequest.GetRequestId() + "&Enc=bin");
try
{
var certCollection = new X509Certificate2Collection();
// Import the downloaded file.
certCollection.Import(p7b);
// Create a PKCS store.
var pfx = new Pkcs12Store();
// Insert root CA certificate into the PKCS store.
pfx.SetCertificateEntry("rootCert",
new X509CertificateEntry(DotNetUtilities.FromX509Certificate(certCollection[0])));
// Get the second certificate from the downloaded file.
// That one is the generated certificate for our request.
var certificateEntry = new X509CertificateEntry[1];
certificateEntry[0] = new X509CertificateEntry(DotNetUtilities.FromX509Certificate(certCollection[1]));
// Insert our certificate with the private key
// under the same alias so then we know that this private key
// is for our certificate.
pfx.SetKeyEntry("taxkey", new AsymmetricKeyEntry(caRequest.PrivateKey), certificateEntry);
var memoryStream = new MemoryStream();
// Stream PFX store using the desired password
// for our file.
pfx.Save(memoryStream, password.ToCharArray(), new SecureRandom());
var pfxBytes = memoryStream.GetBuffer();
pfxBytes = Pkcs12Utilities.ConvertToDefiniteLength(pfxBytes, password.ToCharArray());
// Here you can save the pfxBytes to a file, if you want.
// Actually I needed it to give as a response in MVC application.
return File(pfxBytes, System.Net.Mime.MediaTypeNames.Application.Octet, "NewCert.pfx");
}
catch (Exception ex)
{
// If there is an error remove private key from
// the memory.
caRequest.PrivateKey = null;
caRequest.Request = null;
ErrorSignal.FromCurrentContext().Raise(ex);
if (showError != null && showError.ToLower() == "true")
{
throw ex;
}
return null;
}
The private key is in the CARequest.
/*
using Org.BouncyCastle.Crypto;
*/
public class CARequestModel
{
public AsymmetricKeyParameter PrivateKey { get; set; }
public string Request { get; set; }
}
The private key is saved in the memory until the moment we need it to save to the PFX file, and it is generated at the moment we create the certificate request. So here is the certificate request generating method.
public CARequestModel CertRequest(string subjectDN)
{
var name = new X509Name(subjectDN);
var rsaKeyPairGenerator = new RsaKeyPairGenerator();
rsaKeyPairGenerator.Init(
new KeyGenerationParameters(new SecureRandom(
new CryptoApiRandomGenerator()), _appSettings.PRIVATE_KEY_LENGHT));
// Generate key pair.
var keyPair = rsaKeyPairGenerator.GenerateKeyPair();
// Get the private key.
var privateKey = keyPair.Private;
// Get the public key.
var publicKey = keyPair.Public;
// Set the key usage scope.
var keyUsage = new KeyUsage(KeyUsage.DigitalSignature);
var extensionsGenerator = new X509ExtensionsGenerator();
extensionsGenerator.AddExtension(X509Extensions.KeyUsage, true, keyUsage);
var attribute = new AttributeX509(
PkcsObjectIdentifiers.Pkcs9AtExtensionRequest, new DerSet(extensionsGenerator.Generate()));
// Create the certificate request
var csr = new Pkcs10CertificationRequest("SHA256withRSA", name, publicKey, new DerSet(attribute), privateKey);
// Get it as DER, because then I have to submit it to the MS CA server.
var csrBytes = csr.GetDerEncoded();
// Return the Request and private key
return
new CARequestModel
{
Request = Convert.ToBase64String(csrBytes),
PrivateKey = privateKey
};
}
I need a RSA public key encoded into DER format.
The key-pair is generated using RSACryptoServiceProvider.
What I'm looking for is the c# equivalent to the java:
PublicKey pubKey = myPair.getPublic();
byte[] keyBytes = pubKey.getEncoded();
I have tried looking into BouncyCastle but got lost so if the solution is there any pointers are welcome.
Using Bouncy Castle:
using Org.BouncyCastle.X509;
using Org.BouncyCastle.Security;
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Crypto.Generators;
using Org.BouncyCastle.Crypto.Parameters;
...
var generator = new RsaKeyPairGenerator ();
generator.Init (new KeyGenerationParameters (new SecureRandom (), 1024));
var keyPair = generator.GenerateKeyPair ();
RsaKeyParameters keyParam = (RsaKeyParameters)keyPair.Public;
var info = SubjectPublicKeyInfoFactory.CreateSubjectPublicKeyInfo (keyParam);
RsaBytes = info.GetEncoded ();
The last three lines are the ones who take the RSA public key and export it.
I tried to use aforementioned info.GetEncoded() but as a result, I couldn't use it to verify the signature created with the corresponding private key.
What works for me is converting it to a X509Certificate2.
private bool VerifySignature(string stringContent, string stringSignature, X509Certificate2 x509PublicKey)
{
Int16 cbSignature = 0, cbCert = 0;
int cbData = 0;
CertBasicStruct sCert = new CertBasicStruct();
var pbCert = PublicKey.RawData;
cbCert = (Int16)pbCert.Length;
byte[] pbData = Encoding.UTF8.GetBytes(stringContent);
cbData = (Int16)pbData.Length;
byte[] pbSignature = Convert.FromBase64String(Signature);
cbSignature = (Int16)pbSignature.Length;
var rtn = DllImportService.DecodeCertificate(pbCert, cbCert, ref sCert);
var rtn_1 = DllImportService.VerifySignatureByCert(ref sCert, DllImportService.CKM_SHA256_RSA_PKCS, pbData, cbData, pbSignature, cbSignature);
if (rtn_1 == 0)
{
LogWndAppendLine("Signature is valid.");
return true;
}
else
{
LogWndAppendLine("Signature is invalid");
return false;
}
}
X509Certificate2.RawData would be what you are looking for.