Sign PDF with Smartcard PKS11 through Windows Certificate Store - c#

My project consists in signing a PDF file using PKS11 (usb smartcard).
What I need is:
1) Plug in the USB smart card (and let the OS load the certificate in CurrentUser store (Windows))
2) Call an application , no GUI needed, that sign a determined PDF with the loaded cert.
Step 2 would need the user to input the PIN of the smartcard. For now that is not a concern, I can statically set it for testing purposes.
The posts I have been reading are:
checking-for-the-accessibility-of-smart-card-private-keys-in-windows-10
find-certificate-on-smartcard-currently-on-reader
load-a-smart-card-or-other-private-certificate-in-cryptoserviceprovider-for-sign
how-do-i-sign-a-pdf-document-using-a-certificate-from-the-windows-cert-store
sign-pdf-with-itextsharp-5-3-3-and-usb-token
// Set up the PDF IO
PdfReader reader = new PdfReader(#"C:\Users\martin\Documents\tosign.pdf");
PdfStamper stamper = PdfStamper.CreateSignature(reader,
new FileStream(#"C:\Users\martin\Documents\SignedPdf.pdf", FileMode.Create),
'\0');
PdfSignatureAppearance sap = stamper.SignatureAppearance;
sap.Reason = "For no apparent reason";
sap.Location = "Place";
var certStore = new X509Store(StoreName.My, StoreLocation.CurrentUser);
certStore.Open(OpenFlags.ReadOnly);
X509Store store = new X509Store(StoreName.My, StoreLocation.CurrentUser);
store.Open(OpenFlags.ReadOnly);
X509Certificate2Collection collection = (X509Certificate2Collection)store.Certificates;
X509Certificate2Collection fcollection = (X509Certificate2Collection)collection.Find(X509FindType.FindByTimeValid, DateTime.Now, false);
X509Certificate2 cert = fcollection[0];
BcX509.X509CertificateParser cp = new BcX509.X509CertificateParser();
Org.BouncyCastle.X509.X509Certificate[] chain = new Org.BouncyCastle.X509.X509Certificate[] {
cp.ReadCertificate(cert.RawData)
};
Console.WriteLine(cert);
X509Certificate2 signatureCert = new X509Certificate2(cert);
IExternalSignature externalSignature = new X509Certificate2Signature(cert, "SHA-1");
MakeSignature.SignDetached(sap, externalSignature, chain, null, null, null, 0, CryptoStandard.CMS);
I get a runtime error
System.ArgumentException: 'Unknown encryption algorithm
System.Security.Cryptography.RSACng'
in the line
IExternalSignature externalSignature = new X509Certificate2Signature(cert, "SHA-1");
I read somewhere that externalSignature is to be implemented with custom code depending on the USB vendor. Though in all the posts mentioned here, it seems that line should work with no problems. I tried to change the algorithm to SHA-256. Also I have been digging Github code of IExternalSignature interface to try to get a grasp out of it.
In theory, I know the private key of the USB device will never be accessible, therefore I can not just try to sign with a "getprivatekey" method kind.
In practice, I am a MEAN stack and Python dev, I have never coded in C#.

Related

I want to create a p12 file based on a cert file and a key file

Both the cert and the key file are of type string.
I tried using this:
RSACryptoServiceProvider rsaKey = new RSACryptoServiceProvider();
rsaKey.ImportParameters(keyfile);
X509Certificate2 cert = new X509Certificate2(certfile);
cert.PrivateKey = rsaKey;
cert.Export(X509ContentType.Pkcs12, "xyz");
RestAsynchronicClient client = new RestAsynchronicClient(url, RestDataStandard.JSON, null, cert, logger);
Here I am getting conversion error in the second line. Conversion error from string to RSAParameter.
There's no automatic conversion from a file path, or file contents, to an RSAParameters; the RSACryptoServiceProvider is out of date and not recommended for new code, and the PrivateKey property on certificates is fully [Obsolete] in new versions of .NET.
With .NET 5+, this is easy:
byte[] pfxBytes;
using (X509Certificate2 cert = X509Certificate2.CreateFromPemFile(certFile, keyFile))
{
pfxBytes = cert.Export(X509ContentType.Pkcs12, pfxPwd);
}
Or, in the style closer to the code you've written:
byte[] pfxBytes;
using (X509Certificate2 cert = new X509Certificate2(certFile))
using (RSA key = RSA.Create())
{
key.ImportFromPem(File.ReadAllText(keyFile));
using (X509Certificate2 certWithKey = cert.CopyWithPrivateKey(key))
{
pfxBytes = certWithKey.Export(X509ContentType.Pkcs12, pfxPwd);
}
}
Your reference snippet then goes on to ignore the PFX/PKCS12 output and pass the cert to RestAsynchronicClient. Because of some idiosyncrasies on Windows, that generally won't work if you load the cert from this style. But, if you load the PFX into a new X509Certificate2 object, that'll be in a slightly different state and everything'll be happy.
RestAsynchronicClient client = new RestAsynchronicClient(
url,
RestDataStandard.JSON,
null,
new X509Certificate2(pfxBytes, pfxPwd),
logger);

Digital signature with iText7

I've encountered a problem when trying to implement digital signature with iText7. According to the documentation of iText and couple examples I need to implement IExternalSignature (here), like so: IExternalSignature signature = new PrivateKeySignature(pk, digestAlgorithm); but this is where I get the exception:
var pk = Org.BouncyCastle.Security.DotNetUtilities.GetKeyPair(cert.PrivateKey).Private;
which basically means "Invalid key to use in the current state"(?).
Most of the examples are from older version of iText library and Java (I'm using C#) and I cant quite figute it out.
I'll be very grateful for any tips. Thanks!
EDIT:
Here's some example code just to replicate the exception:
static void Main(string[] args)
{
string output = "D:/Development/TestApp/testOutputMoje.pdf";
string input = "D:/Development/TestApp/testInput.pdf";
PdfReader reader = new PdfReader(input);
string digestAlgorithm = DigestAlgorithms.SHA256;
List<X509Certificate2> oCertChain = new List<X509Certificate2>();
//getting certificates from store
X509Store store = new X509Store(StoreName.My);
store.Open(OpenFlags.ReadOnly);
foreach (var oCert in store.Certificates)
{
oCertChain.Add(oCert);
}
store.Close();
//siginig with first certificate - just example
var cert = oCertChain[0];
//exception here:
var pk = Org.BouncyCastle.Security.DotNetUtilities.GetKeyPair(cert.PrivateKey).Private;
X509Certificate[] bouncyCert = { Org.BouncyCastle.Security.DotNetUtilities.FromX509Certificate(cert) };
StampingProperties stampProp = new StampingProperties();
stampProp.PreserveEncryption();
IExternalSignature signature = new PrivateKeySignature(pk, digestAlgorithm);
PdfSigner signer = new PdfSigner(reader, new FileStream(output, FileMode.Create), stampProp);
signer.SignDetached(signature, bouncyCert, null, null, null, 0, CryptoStandard.CADES);
reader.Close();
}
Couple more informations about the certificate (I cant show You any details unfortunately)
problem is in certificate. It has to be marked as "Exportable". I don't know how solve this case, but in my case I used own certificate this way:
var cert = new X509Certificate2();
cert.Import(File.ReadAllBytes(#"C:\temp\certificate.cer"), "password", X509KeyStorageFlags.Exportable);
This works.
You need to find the way to change your certificate flag. Maybe something with store.Open(OpenFlags.MaxAllowed), but this doesn't work.

Copy cert from LocalComputer to CurrentUser with C#

As the title says, I need to programmatically copy a cert (given the thumbprint) from the LocalComputer store to the CurrentUser store. I've been digging around in the X509Certificate2 definitions and trying stuff, but nothing seems to be working. Here is what I have so far
certPath = "#"C:\%temp%\Cert.pfx";
certPass = "CertPassHere";
X509Store localMachineStore = new X509Store(StoreName.My, StoreLocation.LocalMachine);
localMachineStore.Open(OpenFlags.ReadOnly);
X509Certificate2Collection certificate = localMachineStore.Certificates.Find(X509FindType.FindByThumbprint, "certThumbprint", true);
byte[] rawCertData = certificate[0].Export(X509ContentType.Pfx, certPass);
File.WriteAllBytes(certPath, rawCertData);
localMachineStore.Close();
X509Certificate2Collection collection = new X509Certificate2Collection();
collection.Import(certPath, certPass, X509KeyStorageFlags.PersistKeySet);
X509Store currentUserStore = new X509Store(StoreName.My, StoreLocation.CurrentUser);
foreach (X509Certificate2 cert in collection)
{
Console.WriteLine("Subject is: '{0}'", cert.Subject);
Console.WriteLine("Issuer is: '{0}'", cert.Issuer);
currentUserStore.Add(cert);
}
currentUserStore.Close();
File.Delete(certPath);
I feel like I'm somewhat on the right track here, but any help is very much appreciated :)
Saving it to a file is unnecessary, since you can load the PFX from the byte[] directly.
You don't seem to have a call to currentUserStore.Open(OpenFlags.ReadWrite), is the code finishing, or throwing a CryptographicException?
You need to import the PFX with X509KeyStorageFlags.UserKeySet because the private key will "remember" that it was exported from the machine keystore, and it wants to go back there.
Passing true as the third argument to X509Certificate2Collection.Find is usually not what you want, it's mostly only handy when searching by subject, to ignore expired things.
.
string thumbprint = IAssumeYouHaveThisForReal();
X509Certificate2Collection certificates = localMachineStore.Certificates.Find(
X509FindType.FindByThumbprint,
thumbprint,
false);
byte[] tempPfx = certificates[0].Export(X509ContentType.Pfx, "hi");
X509Certificate2 copyWithUserKey = new X509Certificate2(
tempPfx,
"hi",
X509KeyStorageFlags.UserKeySet /*| X509KeyStorageFlags.Exportable if you like */);
X509Store currentUserMy = new X509Store(StoreName.My, StoreLocation.CurrentUser);
currentUserMy.Open(OpenFlags.ReadWrite);
currentUserStore.Add(copyWithUserKey);
certificates = currentUserStore.Certificates.Find(
X509FindType.FindByThumbprint,
thumbprint,
false);
if (certificates.Count != 1)
throw new InvalidOperationException();

Generate Apple Passbook Coupon/S-MIME Signature in Visual Studio

I am trying to create a Apple wallet passes in my system, after reading Different S/MIME signature between OpenSSL and C# and Apple Passbook coupons from C#, the system can create .pkasss automatically now.
And my problem is signature cannot create successfully in actual. If I using iPhone and try to open the .pkpass file, it can't be open!! I find out that is the problem is coming form signature, if I using mac to create a signature in terminal, it create a 3326bytes size signature; my code only can create a 3002 bytes file, which means the signature must be miss something.
Does Mac OS X method have a big difference between Windows OS method?
Has anyone faced this problem before? Does anyone know why the signatures are different?
Does anyone know how t fix it?
This is my source code:
var cert = new X509Certificate2(assetsFolder + p12File, p12Password);
var buffer = File.ReadAllBytes(Path.Combine(assetsFolder, "manifest.json"));
var cont = new ContentInfo(buffer);
var cms = new SignedCms(cont, true);
var signer = new CmsSigner(cert)
{
IncludeOption = X509IncludeOption.ExcludeRoot,
SignerIdentifierType = SubjectIdentifierType.IssuerAndSerialNumber,
};
cms.ComputeSignature(signer, true);
var myCmsMessage = cms.Encode();
File.WriteAllBytes(Path.Combine(assetsFolder, "signature"), myCmsMessage);
Big Thanks!!!
----------------------------------UPDATE---------------------------------
I found the ans of signature!!!
The setting of OID and SignerIdentifierType will affert the signature
Here is my solution:
byte[] buffer = File.ReadAllBytes(Path.Combine(assetsFolder, "manifest.json"));
X509Certificate2 cert = new X509Certificate2(assetsFolder + p12File, p12Password);
var oid = new Oid("1.2.840.113549.1.7.2");
ContentInfo contentInfo = new ContentInfo(oid, buffer);
SignedCms signedCms = new SignedCms(contentInfo, true);
var cmsSigner = new CmsSigner(cert);
cmsSigner.IncludeOption = X509IncludeOption.ExcludeRoot;
cmsSigner.SignedAttributes.Add(new Pkcs9SigningTime(DateTime.Now));
cmsSigner.SignerIdentifierType = SubjectIdentifierType.SubjectKeyIdentifier;
signedCms.ComputeSignature(cmsSigner);
byte[] myCmsMessage = signedCms.Encode();
return myCmsMessage;

Use a PFX File X509 for Encrypt/Decrypt

I have a pfx file that is for test purposes and have no special security.
I need to use this file to encrypt a XML file. Scenario is that I need to send a Password-Zip file to a client along with a Metadata file relating to the Zip. The Zip will actually contain file for updating a software installation. At first, i though I'd encrypt the Zip itself. But then, it was decided that it'll be protected with a simple password and will be uploaded to a web site (ASP.NET) at client's. The client will also upload a metadata file that will be given with the Zipped update. This Metadata file will have stuff like Client identifier, MAC Address, MD5 Hash of update zip file etc... I need to encrypt this metadata file before sending and decrypt once the client uploads both ZIP and Meta. Once decrypted, I'll try to verify client and the current installation and the patch itself.
The PFX is generated for me.
What will I need to extract from the PFX
I can use BouncyCastle here.
The idea was that once the client install the software at his end, he'll generate a X509 certificate request. That will create two thing: the request and a private key. The client will keep the private key and send us the request. We'll validate the client (payment and all) and generate a certificate for him and email him back (the PFX). He'll then import it to unlock the features on the software and be able to get zipped updates and further stuff
The question is a bit of a complete solution demand like but I'm facing a real hard time then it comes to signing with X509. I'm looking at BouncyCastle docs right now.
Thanks for the read. Any help will be appreciated.
sry bout the long answer.
Yes you can use bouncycastle to extract a certificate from a pfx.
var pkcs = new Pkcs12Store(File.Open("path.pfx", FileMode.Open), "password".ToCharArray());
pkcs.Aliases // is a list of certificate names that are in the pfx;
pkcs.GetCertificate(alias); // gets a certificate from the pfx
We use a very similar approach but we send the certificate without the private key back in PEM, since it doesn't contain any "secret's"
This is how you make a certificate signing request:
//generate a privatekey and public key
RsaKeyPairGenerator rkpg1 = new RsaKeyPairGenerator();
rkpg1.Init(new KeyGenerationParameters(new SecureRandom(), Keystrength));
AsymmetricCipherKeyPair ackp1 = rkpg1.GenerateKeyPair();
RsaPrivateCrtKeyParameters privateKey = (RsaPrivateCrtKeyParameters)ackp1.Private;
RsaKeyParameters publicKey = (RsaKeyParameters)ackp1.Public;
X509Name comonname = new X509Name (cname);
Pkcs10CertificationRequest csr = new Pkcs10CertificationRequest ("SHA1WITHRSA", comonname, publicKey, null, privateKey);
csr.Verify ();
StringBuilder sb = new StringBuilder();
PemWriter pw = new PemWriter (new StringWriter (sb));
pw.WriteObject (csr);
pw.Writer.Flush ();
var pemstring = sb.ToString ();
This is what happens at the server end signing the certificate signing request:
As far as I understand the root certificate can be any certificate of which you have a private key. And if you want to use it in other application like a browser it needs to be inserted in to the certificate store called "Trusted Root Certificate Authorities", if it is a self signed certificate, or "Trusted Intermediate Certificate Authorities", if it isn't self signed at the client computer without the pirvate key off course.
A certificate with a private key is identifiable by the little key added to the icon see here;
//rootCert contains the rootcertificate in bouncycastle format
var pemstring = "a string containing the PEM";
PemReader pr = new PemReader (new StringReader (pemstring));
Pkcs10CertificationRequest csr = (Pkcs10CertificationRequest)pr.ReadObject ();
X509V3CertificateGenerator certgen = new X509V3CertificateGenerator();
certgen.SetSubjectDN(csr.GetCertificationRequestInfo().Subject);
certgen.SetIssuerDN(rootCert.SubjectDN);
certgen.SetPublicKey(csr.GetPublicKey());
certgen.SetSignatureAlgorithm(csr.SignatureAlgorithm.ObjectID.Id);
certgen.SetNotAfter(validThrough); //a datetime object
certgen.SetNotBefore(validFrom); //a datetime object
certgen.SetSerialNumber(serialNumber); //a biginteger
X509Certificate clientcert = certgen.Generate(rootPrivateKey);
//to send the certificate without the private key to the client you'll have to
//convert it to PEM:
StringBuilder sb = new StringBuilder();
PemWriter pw = new PemWriter (new StringWriter (sb));
pw.WriteObject (clientcert);
pw.Writer.Flush ();
var pemstring = sb.ToString ();
//to make it a .net certificate use:
X509Certificate2 netcert = DotNetUtilities.ToX509Certificate (clientcert);
The client certificatie does NOT contain a private key atm. Thats what happens at the client end again like so:
//where pemstring contains the certificate in a PEMstring like shown above.
//and privateKey is the one we had in the first part over at the client.
PemReader pr = new PemReader(new StringReader(pemstring));
X509Certificate2 cert = DotNetUtilities.ToX509Certificate((Bouncy.X509Certificate)pr.ReadObject());
CspParameters cparms = new CspParameters
{
CryptoKeySecurity = new CryptoKeySecurity(),
Flags = CspProviderFlags.UseMachineKeyStore
};
RSACryptoServiceProvider rcsp = new RSACryptoServiceProvider(cparms);
RSAParameters parms = new RSAParameters
{
Modulus = privateKey.Modulus.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 (),
Exponent = privateKey.PublicExponent.ToByteArrayUnsigned ()
};
rcsp.ImportParameters(parms);
netcert.PrivateKey = rcsp;

Categories