I am trying to create a self-signed certificate and then reading it at some point.
This is the code for creating the pfx file: (source)
public static void CreateSelfSignedCertificate(string subjectName)
{
string pathToCertificate = CommonHelper.MapPath($"path_to_certificate/{TokenSigningCertificateName}");
if (!File.Exists(pathToCertificate))
{
// create DN for subject and issuer
var dn = new CX500DistinguishedName();
dn.Encode("CN=" + subjectName, X500NameFlags.XCN_CERT_NAME_STR_NONE);
// create a new private key for the certificate
CX509PrivateKey privateKey = new CX509PrivateKey();
privateKey.ProviderName = "Microsoft Base Cryptographic Provider v1.0";
privateKey.MachineContext = true;
privateKey.Length = 2048;
privateKey.KeySpec = X509KeySpec.XCN_AT_SIGNATURE; // use is not limited
privateKey.ExportPolicy = X509PrivateKeyExportFlags.XCN_NCRYPT_ALLOW_PLAINTEXT_EXPORT_FLAG;
privateKey.Create();
// Use the stronger SHA512 hashing algorithm
var hashobj = new CObjectId();
hashobj.InitializeFromAlgorithmName(ObjectIdGroupId.XCN_CRYPT_HASH_ALG_OID_GROUP_ID,
ObjectIdPublicKeyFlags.XCN_CRYPT_OID_INFO_PUBKEY_ANY,
AlgorithmFlags.AlgorithmFlagsNone, "SHA512");
// add extended key usage if you want - look at MSDN for a list of possible OIDs
var oid = new CObjectId();
oid.InitializeFromValue("1.3.6.1.5.5.7.3.1"); // SSL server
var oidlist = new CObjectIds();
oidlist.Add(oid);
var eku = new CX509ExtensionEnhancedKeyUsage();
eku.InitializeEncode(oidlist);
// Create the self signing request
var cert = new CX509CertificateRequestCertificate();
cert.InitializeFromPrivateKey(X509CertificateEnrollmentContext.ContextMachine, privateKey, "");
cert.Subject = dn;
cert.Issuer = dn; // the issuer and the subject are the same
cert.NotBefore = DateTime.Now;
// this cert expires immediately. Change to whatever makes sense for you
cert.NotAfter = DateTime.Now;
cert.X509Extensions.Add((CX509Extension) eku); // add the EKU
cert.HashAlgorithm = hashobj; // Specify the hashing algorithm
cert.Encode(); // encode the certificate
// Do the final enrollment process
var enroll = new CX509Enrollment();
enroll.InitializeFromRequest(cert); // load the certificate
enroll.CertificateFriendlyName = subjectName; // Optional: add a friendly name
string csr = enroll.CreateRequest(); // Output the request in base64
// and install it back as the response
enroll.InstallResponse(InstallResponseRestrictionFlags.AllowUntrustedCertificate,
csr, EncodingType.XCN_CRYPT_STRING_BASE64, ""); // no password
// output a base64 encoded PKCS#12 so we can import it back to the .Net security classes
var base64encoded = enroll.CreatePFX("", // no password, this is for internal consumption
PFXExportOptions.PFXExportChainWithRoot);
File.WriteAllText(pathToCertificate, base64encoded);
}
}
And this is the code for loading it:
public static X509Certificate2 GetTokenSigningCertificate()
{
string pathToCertificate = CommonHelper.MapPath($"path_to_certificate/{TokenSigningCertificateName}");
X509Certificate2 certificate = new X509Certificate2(pathToCertificate, "");
return certificate;
}
The problem is I am getting:
An error occurred during encode or decode operation.
System.Security.Cryptography.CryptographicException
at
System.Security.Cryptography.CryptographicException.ThrowCryptographicException(Int32
hr) at
System.Security.Cryptography.X509Certificates.X509Utils._LoadCertFromFile(String
fileName, IntPtr password, UInt32 dwFlags, Boolean persistKeySet,
SafeCertContextHandle& pCertCtx) at
System.Security.Cryptography.X509Certificates.X509Certificate.LoadCertificateFromFile(String
fileName, Object password, X509KeyStorageFlags keyStorageFlags) at
System.Security.Cryptography.X509Certificates.X509Certificate2..ctor(String
fileName, String password)
Any ideas?
You seem to be exporting your PFX as base64. Unlike certificates, keys, PKCS#7 blobs, and PKCS#8 blobs, PKCS#12/PFX blobs have no defined PEM header. As a consequence, the PFX reading pipeline probably doesn't have a Base64-decode attached to it.
So the simple answer is likely to emit it with the binary encoding (and thus File.WriteAllBytes()) instead of base64.
Related
I'm using syncfusion pdf to for digitally signing pdf files. On their website, they have an example for external signing a PDF. I slightly changed the example so the certificate can be selected from the windows certificate store. The selected certificate requires a PIN, so that a dialog pop up. The function Signature_ComputeHast is plain C# code. How can I enter the PIN programmatically in the code below?
Original example: https://help.syncfusion.com/file-formats/pdf/working-with-digitalsignature#externally-sing-a-pdf-document
private static X509Certificate2 SelectCertificate()
{
// Selecte certificate from store
var store = new X509Store(StoreName.My, StoreLocation.CurrentUser);
store.Open(OpenFlags.ReadOnly | OpenFlags.OpenExistingOnly);
X509Certificate2Collection collection = (X509Certificate2Collection)store.Certificates;
X509Certificate2Collection selectedCertificate = (X509Certificate2Collection)collection.Find(X509FindType.FindByKeyUsage, X509KeyUsageFlags.DigitalSignature, false);// (X509FindType.FindByKeyUsage, X509KeyUsageFlags.DigitalSignature, true);
selectedCertificate = X509Certificate2UI.SelectFromCollection(
store.Certificates,
"Certficates",
"Select a certificate for signing the document",
X509SelectionFlag.SingleSelection);
return selectedCertificate[0];
}
void Signature_ComputeHash(object sender, PdfSignatureEventArgs arguments)
{
//Get the document bytes
byte[] documentBytes = arguments.Data;
SignedCms signedCms = new SignedCms(new ContentInfo(documentBytes), detached: true);
//Compute the signature using the specified digital ID file and the password
X509Certificate2 certificate = SelectCertificate();
var cmsSigner = new CmsSigner(certificate);
//Set the digest algorithm SHA256
cmsSigner.DigestAlgorithm = new Oid("2.16.840.1.101.3.4.2.1");
signedCms.ComputeSignature(cmsSigner);
//Embed the encoded digital signature to the PDF document
arguments.SignedData = signedCms.Encode();
}
You will need to use the RSACryptoServiceProvider or RSACng cng (in .NET core) classes which support Hardware Security Modules to a greater level of granularity on Windows. You can create a new instance of the appropriate class with parameters to include the password as follows:
if(certificate.PrivateKey is RSACryptoServiceProvider rsa) {
if(rsa.CspKeyContainerInfo.HardwareDevice) {
CspParameters cspParams = new CspParameters(1, rsa.CspKeyContainerInfo.ProviderName,
rsa.CspKeyContainerInfo.UniqueKeyContainerName) {
KeyPassword = certPassword,
Flags = CspProviderFlags.NoPrompt
};
byte[] signedCMSBytes = new RSACryptoServiceProvider(cspParams).SignData(documentBytesDigest);
}
}
As far as I am aware you will need to create the hashed digest of documentBytes digest and put that in the in PKCS#7 along with the desired authenticated attributes yourself prior to signing. After that you will need to add any unauthenticated attributes to the CMS. Personally I do all that using Bouncy Castle. But we have to use the MS SSC to interact with the OS for access to the HSM. There is potentially a way to do it all with the SSC classes. Incidentally certPassword is a SecureString.
I'm writing an app that needs to make some HTTPs requests that use a client certificate. However, I can't find any documents on how to install the certificate and then load it for use. I know that you can use the certificate by making a HttpBaseProtocolFilter and adding a certificate but how do you load the certificate for use here? And if you have a .pfx file with your client certificate, how do you install it with your package?
Thanks in advance!
For what it's worth, I ended up figuring this out using a mix of the Portable.BouncyCastle NuGet package and some UWP APIs. Some sample (pseudo-ish) code for what I did is below:
// Asymmetric key pair
RsaKeyPairGenerator keyPairGenerator = new RsaKeyPairGenerator();
keyPairGenerator.Init(
new KeyGenerationParameters(
new SecureRandom(new CryptoApiRandomGenerator()), 2048));
AsymmetricCipherKeyPair keyPair = keyPairGenerator.GenerateKeyPair();
// Create certificate
X509V3CertificateGenerator generator = new X509V3CertificateGenerator();
generator.SetSubjectDN("foo");
generator.SetIssuerDN("foo");
generator.SetSerialNumber(new BigInteger("12345").Abs());
generator.SetNotBefore(DateTime.UtcNow);
generator.SetNotAfter(DateTime.UtcNow + TimeSpan.FromYears(1));
generator.SetPublicKey(keyPair.Public);
BouncyCastleX509Certificate certificate =
generator.Generate(
new Asn1SignatureFactory("SHA1WithRSA", keyPair.Private));
// Create PKCS12 certificate bytes.
Pkcs12Store store = new Pkcs12Store();
X509CertificateEntry certificateEntry = new X509CertificateEntry(certificate);
string friendlyName = "Friendly Name";
string password = "password";
store.SetCertificateEntry(friendlyName, certificateEntry);
store.SetKeyEntry(
friendlyName,
new AsymmetricKeyEntry(keyPair.Private),
new X509CertificateEntry[] { certificateEntry });
string pfxData;
using (MemoryStream memoryStream = new MemoryStream(512))
{
store.Save(memoryStream, password.ToCharArray(), this.SecureRandom);
pfxData = CryptographicBuffer.EncodeToBase64String(memoryStream.ToArray().AsBuffer());
}
// Add the certificate to the cert store
await CertificateEnrollmentManager.ImportPfxDataAsync(
pfxData,
password,
ExportOption.NotExportable,
KeyProtectionLevel.NoConsent,
InstallOptions.DeleteExpired,
friendlyName);
// Read the UWP cert from the cert store
Certificate uwpCertificate =
(await CertificateStores.FindAllAsync(
new CertificateQuery { FriendlyName = friendlyName }))[0];
// Create the UWP HTTP client.
HttpBaseProtocolFilter filter = new HttpBaseProtocolFilter();
filter.IgnorableServerCertificateErrors.Add(ChainValidationResult.Untrusted);
filter.IgnorableServerCertificateErrors.Add(ChainValidationResult.InvalidName);
filter.ClientCertificate = uwpCertificate;
HttpClient httpClient = new HttpClient(filter);
// Profit!
I am building login workflow using Google for user. Once user is authenticated, I call GetAuthResponse to get idToken.
https://developers.google.com/identity/sign-in/web/backend-auth
Now, I need to verify certificate against Google certificate. I am using JwtSecurityToken(C#) for the same.
I am referencing for verification - http://blogs.msdn.com/b/alejacma/archive/2008/06/25/how-to-sign-and-verify-the-signature-with-net-and-a-certificate-c.aspx
Issue is - I always gets false from VerifyHash. As, VerifyHash returns just false without any reason, I am not able to find way to verify whether idToken is
valid or not. My code is given below
String strID = ""; // idToken received from Google AuthResponse
JwtSecurityToken token = new JwtSecurityToken(strID);
byte[] text = GetHash(token.RawData);
SHA256Cng sha1 = new SHA256Cng();
UnicodeEncoding encoding = new UnicodeEncoding();
byte[] data = encoding.GetBytes(text);
byte[] hash = sha1.ComputeHash(data);
byte[] signature = Encoding.Unicode.GetBytes(token.RawSignature);
// Modulus and exponent value from https://www.googleapis.com/oauth2/v2/certs - second set of keys
String modulus = "uHzGq7cMlx21nydbz9VsW1PItetb9mqvnpLp_8E3Knyk-mjv9DlaPhKGHYlJfHYGzKa2190C5vfsLLb1MIeGfdAv7ftpFsanIWawl8Zo0g-l0m7T2yG_7XerqcVK91lFifeJtgxKI86cPdZkgRy6DaYxMuAwAlhvpi3_UhPvsIwi7M6mxE8nUNpUWodh_YjJNu3wOxKDwbBZuRV2itjY6Z7RjFgJt1CsKF-QjqSVvWjAl0LaCaeMS_8yae0ln5YNeS8rAb6xkmcOuYeyhYsiBzwLRvgpXzEVLjLr631Z99oUHTpP9vWJDpGhfkrClkbmdtZ-ZCwX-eFW6ndd54BJEQ==";
String exponent = "AQAB";
modulus = modulus.Replace('-', '+').Replace('_', '/'); // Else it gives Base64 error
StringBuilder sb = new StringBuilder();
sb.Append("<RSAKeyValue>");
sb.Append("<Modulus>");
sb.Append(modulus);
sb.Append("</Modulus>");
sb.Append("<Exponent>");
sb.Append(exponent);
sb.Append("</Exponent>");
sb.Append("</RSAKeyValue>");
RSACryptoServiceProvider RSAVerifier = new RSACryptoServiceProvider();
RSAVerifier.FromXmlString(sb.ToString());
// Verify the signature with the hash
return RSAVerifier.VerifyHash(hash, CryptoConfig.MapNameToOID("SHA256"), signature);
You might want to try as done in the Google+ Token Verification project - this fork includes a few minor updates that are still in review.
An alternative approach is to just verify the tokens using Google's token verification endpoints:
curl https://www.googleapis.com/oauth2/v2/tokeninfo?id_token=eyJhbGciOiJSUzI1NiIsImtpZCI6IjkyNGE0NjA2NDgxM2I5YTA5ZmFjZGJiNzYwZGI5OTMwMWU0ZjBkZjAifQ.eyJpc3MiOiJhY2NvdW50cy5nb29nbGUuY29tIiwic3ViIjoiMTEwNTcwOTc3MjI2ODMwNTc3MjMwIiwiYXpwIjoiMzY0MzgxNDQxMzEwLXRuOGw2ZnY2OWdnOGY3a3VjanJhYTFyZWpmaXRxbGpuLmFwcHMuZ29vZ2xldXNlcmNvbnRlbnQuY29tIiwiYXRfaGFzaCI6IlAzLU1HZTdocWZhUkZ5Si1qcWRidHciLCJhdWQiOiIzNjQzODE0NDEzMTAtdG44bDZmdjY5Z2c4ZjdrdWNqcmFhMXJlamZpdHFsam4uYXBwcy5nb29nbGV1c2VyY29udGVudC5jb20iLCJjX2hhc2giOiJjd3hsdXBUSkc4N2FnbU1pb0tSYUV3IiwiaWF0IjoxNDM0NDcyODc2LCJleHAiOjE0MzQ0NzY0NzZ9.Gz_WljZOV9NphDdClakLstutEKk65PNpEof7mxM2j-AOfVwh-SS0L5uxIaknFOk4-nDGmip42vrPYgNvbQWKZY63XuCs94YQgVVmTNCTJnao1IavtrhYvpDqGuGKdEB3Wemg5sS81pEthdvHwyxfwLPYukIhT8-u4ESfbFacsRtR77QRIOk-iLJAVYWTROJ05Gpa-EkTunEBVmZyYetbMfSoYkbwFKxYOlHLY-ENz_XfHTGhYhb-GyGrrw0r4FyHb81IWJ6Jf-7w6y3RiUJik7kYRkvnFouXUFSm8GBwxsioi9AAkavUWUk27s15Kcv-_hkPXzVrW5SvR1zoTI_IMw
I used Bouncy Castle to generate a private key, as well as a PKCS10 CSR, which I then send to a remote server to be signed. I get back a standard base64 encoded signed SSL certificate in response as a string. The question is, how do I import the signed certificate from a string, and then save both the private key and signed certificate as a PKCS12 (.PFX) file?
Additionally, how do I bundle in a CA certificate to be included within the PFX file?
// Generate the private/public keypair
RsaKeyPairGenerator kpgen = new RsaKeyPairGenerator ();
CryptoApiRandomGenerator randomGenerator = new CryptoApiRandomGenerator ();
kpgen.Init (new KeyGenerationParameters (new SecureRandom (randomGenerator), 2048));
AsymmetricCipherKeyPair keyPair = kpgen.GenerateKeyPair ();
// Generate the CSR
X509Name subjectName = new X509Name ("CN=domain.com/name=Name");
Pkcs10CertificationRequest kpGen = new Pkcs10CertificationRequest ("SHA256withRSA", subjectName, keyPair.Public, null, keyPair.Private);
string certCsr = Convert.ToBase64String (kpGen.GetDerEncoded ());
// ** certCsr is now sent to be signed **
// ** let's assume that we get "certSigned" in response, and also have the CA **
string certSigned = "[standard signed certificate goes here]";
string certCA = "[standard CA certificate goes here]";
// Now how do I import certSigned and certCA
// Finally how do I export everything as a PFX file?
Bouncy Castle is a very powerful library, however the lack of documentation makes it quite difficult to work with. After searching for much too long through all of the classes and methods I finally found what I was looking for. The following code will take the previously generated private key, bundle it together with the signed certificate and the CA, and then save it as a .PFX file:
// Import the signed certificate
X509Certificate signedX509Cert = new X509CertificateParser ().ReadCertificate (Encoding.UTF8.GetBytes (certSigned));
X509CertificateEntry certEntry = new X509CertificateEntry (signedX509Cert);
// Import the CA certificate
X509Certificate signedX509CaCert = new X509CertificateParser ().ReadCertificate (Encoding.UTF8.GetBytes (certCA ));
X509CertificateEntry certCaEntry = new X509CertificateEntry (signedX509CaCert);
// Prepare the pkcs12 certificate store
Pkcs12Store store = new Pkcs12StoreBuilder ().Build ();
// Bundle together the private key, signed certificate and CA
store.SetKeyEntry (signedX509Cert.SubjectDN.ToString () + "_key", new AsymmetricKeyEntry (keyPair.Private), new X509CertificateEntry[] {
certEntry,
certCaEntry
});
// Finally save the bundle as a PFX file
using (var filestream = new FileStream (#"CertBundle.pfx", FileMode.Create, FileAccess.ReadWrite)) {
store.Save (filestream, "password".ToCharArray (), new SecureRandom ());
}
Feedback and improvements are welcome!
I need to generate a PKCS#7/CMS detached signature, and I know I can do it easily that way :
byte[] data = GetBytesFromFile(cheminFichier);
X509Certificate2 certificate = null;
X509Store my = new X509Store(StoreName.My,StoreLocation.CurrentUser);
my.Open(OpenFlags.ReadOnly);
X509Certificate2Collection certColl = X509Certificate2UI.SelectFromCollection(my.Certificates, "Test" , "Choose a certificate" , X509SelectionFlag.SingleSelection);
certificate = certColl[0];
if (certificate == null) throw new Exception("No certificates found.");
//byte [] pfxFile = certificate.Export(X509ContentType.Pfx);
//X509Certificate2 certPfx = new X509Certificate2(pfxFile);
ContentInfo content = new ContentInfo(new Oid("1.2.840.113549.1.7.1"),data);
SignedCms signedCms = new SignedCms(content, true);
CmsSigner signer = new CmsSigner(certificate);
signer.DigestAlgorithm = new Oid("SHA1"); // sha1
// create the signature
signedCms.ComputeSignature(signer);
return signedCms.Encode();
but! The users of this application imported their certificates using Strong Private Key Protection. I've found some info on that, some people say that type of case can't work in .NET framework, and that surprises me. I'd like to know if anybody has a workaround, or has a solution to this.
Basically my users give me a file name (PDF or RTF), and then I search for their certificate in the My store, I use the private key associated with it to produce the signature. I want at this moment the user to be asked to enter his Private Key password, that way the application doesn't receive the password.