How to create self-signed certificate programmatically for WCF service? - c#

I have a self-hosted WCF server running as a Windows service under the Local System account. I am trying to create a self-signed certificate programmatically in c# for use with a net.tcp endpoint using Message level security.
I am using the following code which is very closely based on the accepted answer in How to create a self-signed certificate using C#? with some small changes trying to solve my problem.
public static X509Certificate2 CreateSelfSignedCertificate(string subjectName, TimeSpan expirationLength)
{
// create DN for subject and issuer
var dn = new CX500DistinguishedName();
dn.Encode("CN=" + subjectName, X500NameFlags.XCN_CERT_NAME_STR_NONE);
CX509PrivateKey privateKey = new CX509PrivateKey();
privateKey.ProviderName = "Microsoft Strong Cryptographic Provider";
privateKey.Length = 1024;
privateKey.KeySpec = X509KeySpec.XCN_AT_KEYEXCHANGE;
privateKey.KeyUsage = X509PrivateKeyUsageFlags.XCN_NCRYPT_ALLOW_DECRYPT_FLAG | X509PrivateKeyUsageFlags.XCN_NCRYPT_ALLOW_KEY_AGREEMENT_FLAG;
privateKey.MachineContext = true;
privateKey.ExportPolicy = X509PrivateKeyExportFlags.XCN_NCRYPT_ALLOW_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, "SHA1");
// 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.Date;
// this cert expires immediately. Change to whatever makes sense for you
cert.NotAfter = cert.NotBefore + expirationLength;
//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);
// instantiate the target class with the PKCS#12 data (and the empty password)
return new System.Security.Cryptography.X509Certificates.X509Certificate2(
System.Convert.FromBase64String(base64encoded), "",
// mark the private key as exportable (this is usually what you want to do)
// mark private key to go into the Machine store instead of the current users store
X509KeyStorageFlags.Exportable | X509KeyStorageFlags.MachineKeySet
);
}
And I store it with this code:
X509Store store = new X509Store(storeName, StoreLocation.LocalMachine);
store.Open(OpenFlags.ReadWrite);
store.Add(newCert);
store.Close();
This creates the certificate and puts it in the LocalMachine certificate store. The problem is that when I try to start the WCF service I get the following exception:
It is likely that certificate 'CN=myCertificate' may not have a private key that is capable of key exchange or the process may not have access rights for the private key. Please see inner exception for detail.
Inner exception: Keyset does not exist
The output of the FindPrivateKey sample (http://msdn.microsoft.com/en-us/library/aa717039%28v=vs.100%29.aspx) for my certificate is:
Private key directory:
C:\ProgramData\Microsoft\Crypto\RSA\MachineKeys
Private key file name:
f0d47c7826b8ef5148b6d412f1c40024_4a8a026f-58e4-40f7-b779-3ae9b6aae1a7
I can see this 1.43KB file in explorer. If I look at the properties|Security I see SYSTEM and Administrators both with Full control.
In researching this error I have seen many answers about the private key missing or incorrect permissions. I can't see what the problem is.
The really strange thing is that if I use the mmc Certificate plugin, go to the certificate and choose All Tasks|Manage Private Keys... I see the same security settings. After viewing this even if I just bring up the dialog and hit the Cancel button the certificate now works correctly in WCF. I can simply restart the service and everything runs perfectly.
If I create a certificate using MakeCert it works just fine from the start. I don't know what it does differently.
One other piece of information that may not be relevant is that the certificate not only gets put in the My store where I told it to get put, but it also gets put in the "Intermediate Certification Authorities" store. I don't know why or if it matters.
So...any ideas what I am doing wrong?
UPDATE: Well, this is not just a WCF issue. I essentially get the same problem when I try to use the certificate to bind to an endpoint with http.sys using HttpSetServiceConfiguration. The method returns 1312 - "A specified logon session does not exist. It may already have been terminated". This is actually not the real error. I saw in the Security Event log an Audit Failure that say this:
Cryptographic Parameters:
Provider Name: Microsoft Software Key Storage Provider
Algorithm Name: Not Available.
Key Name: {A23712D0-9A7B-4377-89DB-B1B39E3DA8B5}
Key Type: Machine key.
Cryptographic Operation:
Operation: Open Key.
Return Code: 0x80090011
0x80090011 is Object was not found. So this appears to be the same problem. Again, after I open the Manage Private Keys dialog for the certificate this works perfectly also.
I am still looking for the cause of the problem.
UPDATE #2: I was able to get this working using the accepted answer below. Interestingly, this code now seems to put the certificate in the Machine store without calling the X509Store code. I still call the code because I am not sure and it does not hurt anything. Here is the final code that I am using to create the certificate.
static public X509Certificate2 CreateSelfSignedCertificate(string subjectName, TimeSpan expirationLength)
{
// create DN for subject and issuer
var dn = new CX500DistinguishedName();
dn.Encode("CN=" + subjectName, X500NameFlags.XCN_CERT_NAME_STR_NONE);
CX509PrivateKey privateKey = new CX509PrivateKey();
privateKey.ProviderName = "Microsoft Strong Cryptographic Provider";
privateKey.Length = 2048;
privateKey.KeySpec = X509KeySpec.XCN_AT_KEYEXCHANGE;
privateKey.KeyUsage = X509PrivateKeyUsageFlags.XCN_NCRYPT_ALLOW_DECRYPT_FLAG | X509PrivateKeyUsageFlags.XCN_NCRYPT_ALLOW_KEY_AGREEMENT_FLAG;
privateKey.MachineContext = true;
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");
// 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.Date;
// this cert expires immediately. Change to whatever makes sense for you
cert.NotAfter = cert.NotBefore + expirationLength;
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);
// instantiate the target class with the PKCS#12 data (and the empty password)
return new System.Security.Cryptography.X509Certificates.X509Certificate2(
System.Convert.FromBase64String(base64encoded), "",
// mark the private key as exportable (this is usually what you want to do)
// mark private key to go into the Machine store instead of the current users store
X509KeyStorageFlags.Exportable | X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.PersistKeySet
);
}

I had the same issue using the equivalent code in PowerShell. It appears that sometime the private key just disappears. I used Process Monitor and you can see the key file being deleted.
The way I solved this was to add X509KeyStorageFlags.PersistKeySet to the X509Certificate2 constructor.

I could not make this work, but I found an alternate solution. (Update December 2014: I have now gotten it to work using the accepted answer.)
I was able to use the PluralSight.Crypto library to achieve what I need. I had to modify the source code slightly to get the private key to store in the LocalMachine store. The changes I made were to the file CryptContext.cs. I changed the CreateSelfSignedCertificate method. Following is a snippet of code including the change that I made. In essence, I set the Flags member of the CryptKeyProviderInformation structure to set it to 0x20 (CRYPT_MACHINE_KEYSET) if the CryptContext object contains this value in its Flags.
byte[] asnName = properties.Name.RawData;
GCHandle asnNameHandle = GCHandle.Alloc(asnName, GCHandleType.Pinned);
int flags = 0; // New code
if ((this.Flags & 0x20) == 0x20) // New code
flags = 0x20; // New code
var kpi = new Win32Native.CryptKeyProviderInformation
{
ContainerName = this.ContainerName,
KeySpec = (int)KeyType.Exchange,
ProviderType = 1, // default RSA Full provider
Flags = flags // New code
};
Then I use the function in my own code like this:
using (Pluralsight.Crypto.CryptContext ctx = new Pluralsight.Crypto.CryptContext()) {
ctx.Flags = 0x8 | 0x20;
ctx.Open();
X509Certificate2 cert = ctx.CreateSelfSignedCertificate(
new Pluralsight.Crypto.SelfSignedCertProperties
{
IsPrivateKeyExportable = true,
KeyBitLength = 4096,
Name = new X500DistinguishedName("CN=" + subjectName),
ValidFrom = DateTime.Today,
ValidTo = DateTime.Today + expirationLength,
});
return cert;
}
Notice that I set the Flags for the CryptContext object to be 0x8 | 0x20 (CRYPT_NEWKEYSET | CRYPT_MACHINE_KEYSET).
I wish I could figure out what was wrong with my original solution. But I need something to work and in my testing this solution does what I need. I hope it helps someone else along the way.

You can also use the CLR Security library on CodePlex (https://clrsecurity.codeplex.com/). Here is sample code which creates a self signed certificate, and tests it with SSLStream.
var machineName = Environment.MachineName;
var keyCreationParameters = new CngKeyCreationParameters();
keyCreationParameters.KeyUsage = CngKeyUsages.AllUsages;
keyCreationParameters.KeyCreationOptions = CngKeyCreationOptions.OverwriteExistingKey;
keyCreationParameters.Parameters.Add(new CngProperty("Length", BitConverter.GetBytes(4096), CngPropertyOptions.None));
var cngKey = CngKey.Create(CngAlgorithm2.Rsa, "Test", keyCreationParameters);
var x500DistinguishedName = new X500DistinguishedName("CN=" + machineName);
x500DistinguishedName.Oid.Value = "1.3.6.1.5.5.7.3.1";
var certificateCreationParameters = new X509CertificateCreationParameters(x500DistinguishedName);
certificateCreationParameters.SignatureAlgorithm = X509CertificateSignatureAlgorithm.RsaSha512;
certificateCreationParameters.TakeOwnershipOfKey = true;
certificateCreationParameters.CertificateCreationOptions = X509CertificateCreationOptions.None;
certificateCreationParameters.EndTime = new DateTime(9999, 12,31, 23, 59, 59, 999, DateTimeKind.Utc);
var certificate = cngKey.CreateSelfSignedCertificate(certificateCreationParameters);
var certificateStore = new X509Store(StoreName.Root, StoreLocation.CurrentUser);
certificateStore.Open(OpenFlags.ReadWrite);
certificateStore.Add(certificate);
certificateStore.Close();
var tcpListener = TcpListener.Create(6666);
tcpListener.Start();
var client = new TcpClient("localhost", 6666);
var acceptedClient = tcpListener.AcceptTcpClient();
var acceptedClinetSslStream = new SslStream(
acceptedClient.GetStream(), false);
var serverAuthTask = acceptedClinetSslStream.AuthenticateAsServerAsync(certificate,
false, SslProtocols.Tls, true);
SslStream clientSslStream = new SslStream(
client.GetStream(),
false,
delegate(object o, X509Certificate x509Certificate, X509Chain chain, SslPolicyErrors errors)
{
if (errors == SslPolicyErrors.None)
return true;
Console.WriteLine("Certificate error: {0}", errors);
// Do not allow this client to communicate with unauthenticated servers.
return false;
},
null);
var clientAuthTask = clientSslStream.AuthenticateAsClientAsync(machineName);
Task.WaitAll(serverAuthTask, clientAuthTask);

Related

Adding a certificate to Store then retrieve it

Disclaimer: I am 2 days into reading about Certificates/RSA Algorithms and Encrypt/Decrypt.
I am trying to do a small app that communicates with Windows Key Store ( Certificate Store ) and where I should be able to read certificates/add certificates.
I have created a method to add a certificate.
public void AddKey()
{
CngKey cngKey;
CngKeyCreationParameters cng = new CngKeyCreationParameters
{
KeyUsage = CngKeyUsages.AllUsages
};
if (!CngKey.Exists(KEY_NAME))
{
cngKey = CngKey.Create(CngAlgorithm.Rsa, KEY_NAME, cng);
}
else
{
cngKey = CngKey.Open(KEY_NAME);
}
RSACng rsaKey = new RSACng(cngKey)
{
KeySize = 2048
};
byte[] rsaPrvKeyExport = rsaKey.Key.Export(CngKeyBlobFormat.GenericPrivateBlob);
byte[] rsaPubKeyExport = rsaKey.Key.Export(CngKeyBlobFormat.GenericPublicBlob);
CngKey cngPrv = CngKey.Import(rsaPrvKeyExport, CngKeyBlobFormat.GenericPrivateBlob);
CngKey cngPub = CngKey.Import(rsaPubKeyExport, CngKeyBlobFormat.GenericPublicBlob);
//var signed = Sign512(Constants.STRING_TO_ENCODE.ToByteArray(), rsaPrvKeyExport);
string exportPrivateKey = Convert.ToBase64String(rsaKey.ExportPkcs8PrivateKey());
string pemString = $"{Constants.RSA_KEY_HEADER}\n{exportPrivateKey}\n{Constants.RSA_KEY_FOOTER}";
X509Store store = new X509Store(StoreName.My, StoreLocation.LocalMachine);
store.Open(OpenFlags.ReadWrite);
var certificate = BuildSelfSignedServerCertificate(rsaKey);
var thumbprint = certificate.Thumbprint;
store.Certificates.Add(certificate);
store.Close();
}
I know not all lines in this code are needed, but I am in the learning process.
So what I do here is create a cngKey
Open the store.
Create a Certificate from my cngKey
Add the certificate to the store
Close the store.
I generate a certificate from a cngKey using this code
private X509Certificate2 BuildSelfSignedServerCertificate(RSA key)
{
X500DistinguishedName distinguishedName = new X500DistinguishedName($"CN={Constants.CERTIFICATE_NAME}");
var request = new CertificateRequest(distinguishedName, key, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
request.CertificateExtensions.Add(
new X509KeyUsageExtension(X509KeyUsageFlags.DataEncipherment | X509KeyUsageFlags.KeyEncipherment | X509KeyUsageFlags.DigitalSignature, false));
request.CertificateExtensions.Add(
new X509EnhancedKeyUsageExtension(
new OidCollection { new Oid("1.3.6.1.5.5.7.3.1") }, false));
//request.CertificateExtensions.Add(sanBuilder.Build());
var certificate = request.CreateSelfSigned(new DateTimeOffset(DateTime.UtcNow.AddDays(-1)), new DateTimeOffset(DateTime.UtcNow.AddDays(3650)));
certificate.FriendlyName = Constants.CERTIFICATE_NAME;
return new X509Certificate2(certificate.Export(X509ContentType.Pfx, "WeNeedASaf3rPassword"), "WeNeedASaf3rPassword", X509KeyStorageFlags.MachineKeySet);
}
This gives no error, but when I open the certificate store I can't find it
I also tried to retrive it programmaticaly
using this bit of code
X509Store store = new X509Store(StoreName.My, StoreLocation.LocalMachine);
store.Open(OpenFlags.ReadOnly);
var certificates = store.Certificates;
but my certificate is not here.
What am I missing, I am sure I am doing something wrong, but being new to this, I have no idea what.
Online I could not find a complete example of what I tried to achieve here.
As #dimitar.bogdanov pointed out in comments, you are not adding the certificate to the store:
store.Certificates.Add(certificate);
here you are adding the certificate only to disconnected collection. Any changes in this collection object will not reflect actual store state. Instead, you have to use X509Store.Add method to update actual store.

WebSocket secure connection self signed certificate

The goal is a web application that exchanges information with an C# application that is installed on the user's pc.
The client application is the websocket server and the browser is the websocket client.
In the end the websocket client in the user's browser is created persistently via Angular and the application is running on the pc and doing some things.
The C# library used is WebSocket-Sharp. The websocket client is normal javascript.
Obviously this connection happens only local so the client connects to localhost.
As the website is secured via HTTPS the websocket has to be secured too. For this purpose the C# application creates a certificate when it starts up (it's just for testing purposes actually).
The connection doesn't works because the certificate is untrusted. All server checks for the client are disabled but the connection won't establish.
This is the part where the server is created
_server = new WebSocketServer($"wss://localhost:4649")
{
SslConfiguration =
{
ServerCertificate = Utils.Certificate.CreateSelfSignedCert(),
ClientCertificateRequired = false,
CheckCertificateRevocation = false,
ClientCertificateValidationCallback = (sender, certificate, chain, sslPolicyErrors) => true
}
};
_server.AddWebSocketService<CommandsBehaviour>("/commands");
_server.AddWebSocketService<NotificationsBehaviour>("/notifications");
_server.Start();
This is how the certificate is created with BouncyCastle
private static AsymmetricKeyParameter CreatePrivateKey(string subjectName = "CN=root")
{
const int keyStrength = 2048;
// Generating Random Numbers
var randomGenerator = new CryptoApiRandomGenerator();
var random = new SecureRandom(randomGenerator);
// The Certificate Generator
var certificateGenerator = new X509V3CertificateGenerator();
// Serial Number
var serialNumber = BigIntegers.CreateRandomInRange(BigInteger.One, BigInteger.ValueOf(long.MaxValue), random);
certificateGenerator.SetSerialNumber(serialNumber);
// Issuer and Subject Name
var subjectDn = new X509Name(subjectName);
var issuerDn = subjectDn;
certificateGenerator.SetIssuerDN(issuerDn);
certificateGenerator.SetSubjectDN(subjectDn);
// Valid For
var notBefore = DateTime.UtcNow.Date;
var notAfter = notBefore.AddYears(70);
certificateGenerator.SetNotBefore(notBefore);
certificateGenerator.SetNotAfter(notAfter);
// Subject Public Key
var keyGenerationParameters = new KeyGenerationParameters(random, keyStrength);
var keyPairGenerator = new RsaKeyPairGenerator();
keyPairGenerator.Init(keyGenerationParameters);
var subjectKeyPair = keyPairGenerator.GenerateKeyPair();
return subjectKeyPair.Private;
}
public static X509Certificate2 CreateSelfSignedCert(string subjectName = "CN=localhost", string issuerName = "CN=root")
{
const int keyStrength = 2048;
var issuerPrivKey = CreatePrivateKey();
// Generating Random Numbers
var randomGenerator = new CryptoApiRandomGenerator();
var random = new SecureRandom(randomGenerator);
ISignatureFactory signatureFactory = new Asn1SignatureFactory("SHA512WITHRSA", issuerPrivKey, random);
// The Certificate Generator
var certificateGenerator = new X509V3CertificateGenerator();
certificateGenerator.AddExtension(X509Extensions.SubjectAlternativeName, false, new GeneralNames(new GeneralName[] { new GeneralName(GeneralName.DnsName, "localhost"), new GeneralName(GeneralName.DnsName, "127.0.0.1") }));
certificateGenerator.AddExtension(X509Extensions.ExtendedKeyUsage, true, new ExtendedKeyUsage((new ArrayList() { new DerObjectIdentifier("1.3.6.1.5.5.7.3.1") })));
// Serial Number
var serialNumber = BigIntegers.CreateRandomInRange(BigInteger.One, BigInteger.ValueOf(Int64.MaxValue), random);
certificateGenerator.SetSerialNumber(serialNumber);
// Signature Algorithm
//const string signatureAlgorithm = "SHA512WITHRSA";
//certificateGenerator.SetSignatureAlgorithm(signatureAlgorithm);
// Issuer and Subject Name
var subjectDn = new X509Name(subjectName);
var issuerDn = new X509Name(issuerName);
certificateGenerator.SetIssuerDN(issuerDn);
certificateGenerator.SetSubjectDN(subjectDn);
// Valid For
var notBefore = DateTime.UtcNow.Date;
var notAfter = notBefore.AddYears(70);
certificateGenerator.SetNotBefore(notBefore);
certificateGenerator.SetNotAfter(notAfter);
// Subject Public Key
var keyGenerationParameters = new KeyGenerationParameters(random, keyStrength);
var keyPairGenerator = new RsaKeyPairGenerator();
keyPairGenerator.Init(keyGenerationParameters);
var subjectKeyPair = keyPairGenerator.GenerateKeyPair();
certificateGenerator.SetPublicKey(subjectKeyPair.Public);
// self sign certificate
var certificate = certificateGenerator.Generate(signatureFactory);
// corresponding private key
var info = PrivateKeyInfoFactory.CreatePrivateKeyInfo(subjectKeyPair.Private);
// merge into X509Certificate2
var x509 = new X509Certificate2(certificate.GetEncoded());
var seq = (Asn1Sequence)Asn1Object.FromByteArray(info.ParsePrivateKey().GetDerEncoded());
if (seq.Count != 9)
{
throw new PemException("malformed sequence in RSA private key");
}
var rsa = RsaPrivateKeyStructure.GetInstance(seq); //new RsaPrivateKeyStructure(seq);
var rsaparams = new RsaPrivateCrtKeyParameters(
rsa.Modulus, rsa.PublicExponent, rsa.PrivateExponent, rsa.Prime1, rsa.Prime2, rsa.Exponent1, rsa.Exponent2, rsa.Coefficient);
x509.PrivateKey = DotNetUtilities.ToRSA(rsaparams);
return x509;
}
This behaviour is logical although it is strange as the cert check shouldn't be performed locally.
Is there a possibility to bypass this problem? I already thought about installing the issuer certificate to the trusted certs but this is not an optimal solution.
Have you tried any of the answers to this question?
To summarize, it looks like there are a few options you could try:
Start Chrome with the --ignore-certificate-errors argument specified.
Start an HTTP server on the same port that takes the same self-signed certificate, browse to it, and accept the certificate, after which you should be able to use the WebSocket connection.
Set the configuration option on Firefox network.websocket.allowInsecureFromHTTPS to true, then use the ws:// rather than the wss:// address.
If all this is is for testing and you have the possibility to control that sort of thing, then I think one or more of those should work. If you need your standard end user to be able to do this, I think you'll a need a different solution. As you've found, it doesn't matter if you set the server up to not care about the certificate, the client has to ultimately decide if it wants to accept the certificate or it won't accept the connection.
#Kdawg answers are correct.
You have no hope to have client browsers accept an insecure connection with server-side tuning only. All the behavior for accepting the unsigned (or self-signed) certificate is on the client side.
I would like to add, on top of #Kdawg's answer that on Windows networks, the most common practice for private organizations is to:
Assign a Windows Server to act as Certificate Authority
Add the Certificate Authority's public root certificate into Windows hosts (either by GPO) or manually
Sign the custom-made certificate with the Windows CA server
It sounds painful, and it is.
If I were you, I would go for making a standard publicly-signed certificate and would run SSL off until it is done.
Look at Let's Encrypt for free SSL certificates for your domain.
My final solution was to create a valid certificate for a subdomain and then changed the A/AAAA records to localhost. This way the connection is trusted over HTTPS.

Exception on attempt to create not self-signed certificate

I want to switch from self-signed certificate per device to pair of certificates, one of which is previously generated, placed in Trusted Root Certificate Authorities store, is same for all devices, and works as root CA for second certificate, which is generated per device, and placed in Personal store.
I would like to not use makecert, since creating signed certificate shows up UI, which I want to avoid. Also, OpenSSL can't be used due to some license-related stuff (although I have working solution with it). So, for now I'm working with small C# tool, based on CertEnroll lib.
This is how I create pfx for first, root CA certificate.
makecert -n "CN=Root CA" -cy authority -r -a sha256 -len 2048 -sv root.pvk root.cer
pvk2pfx -pvk root.pvk -spc root.cer -pfx root.pfx -pi 123 -po 123
To create certificate from C# code, I've referenced questions How to create self-signed certificate programmatically for WCF service? and C# Generate a non self signed client CX509Certificate Request without a CA using the certenroll.dll.
So far, I have following code. Method for certificate generation:
/// <summary>
/// Generates self-signed certificate with specified subject, which will expire after specified timespan.
/// </summary>
public X509Certificate2 CreateCertificate(string subjectName, TimeSpan expirationLength, X509Certificate2 issuer = null)
{
// create DN for subject and issuer
var dn = new CX500DistinguishedName();
dn.Encode("CN=" + subjectName);
var issuerName = new CX500DistinguishedName();
if(issuer != null)
{
issuerName.Encode(issuer.Subject);
}
var privateKey = new CX509PrivateKey
{
ProviderName = "Microsoft Strong Cryptographic Provider",
Length = 2048,
KeySpec = X509KeySpec.XCN_AT_KEYEXCHANGE,
KeyUsage = X509PrivateKeyUsageFlags.XCN_NCRYPT_ALLOW_DECRYPT_FLAG |
X509PrivateKeyUsageFlags.XCN_NCRYPT_ALLOW_KEY_AGREEMENT_FLAG,
MachineContext = true,
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");
var cert = new CX509CertificateRequestCertificate();
cert.InitializeFromPrivateKey(X509CertificateEnrollmentContext.ContextMachine, privateKey, "");
cert.Subject = dn;
if (issuer != null)
cert.Issuer = issuerName;
else
cert.Issuer = dn;
cert.NotBefore = DateTime.Now.Date;
cert.NotAfter = cert.NotBefore + expirationLength;
cert.HashAlgorithm = hashobj; // Specify the hashing algorithm
if(issuer != null)
{
var signerCertificate = new CSignerCertificate();
signerCertificate.Initialize(true, X509PrivateKeyVerify.VerifyAllowUI, EncodingType.XCN_CRYPT_STRING_HEX, issuer.GetRawCertDataString());
cert.SignerCertificate = signerCertificate;
}
cert.Encode();
// Do the final enrollment process
var enroll = new CX509Enrollment();
enroll.InitializeFromRequest(cert); // load the certificate
enroll.CertificateFriendlyName = subjectName; // Optional: add a friendly name
var 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);
// instantiate the target class with the PKCS#12 data (and the empty password)
return new X509Certificate2(Convert.FromBase64String(base64encoded), "",
X509KeyStorageFlags.Exportable | X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.PersistKeySet);
}
Simple application to find existing certificate and create new one based on it:
static void Main(string[] args)
{
var certificateGenerator = new CertificateGenerator();
X509Certificate2 rootCA;
using (var store = new X509Store(StoreName.Root, StoreLocation.LocalMachine))
{
store.Open(OpenFlags.ReadWrite);
rootCA = store.Certificates.OfType<X509Certificate2>()
.FirstOrDefault(c => c.Subject.StartsWith("CN=Root CA", StringComparison.Ordinal));
store.Close();
}
if (rootCA == null)
throw new Exception("Can't find root CA certificate");
var testCert = certificateGenerator.CreateCertificate("Test", new TimeSpan(3650, 0, 0, 0), rootCA);
using (var store = new X509Store(StoreName.My, StoreLocation.LocalMachine))
{
store.Open(OpenFlags.ReadWrite);
store.Add(testCert);
store.Close();
}
}
The thing is, that it works great, if I try to reference certificate not in Trusted Root Certificate Authorities, but in Personal (even if I have password on certificate). But if I try to create certificate based on CA certificate from Trusted Root Certificate Authorities, I receive exception on signerCertificate.Initialize, saying
Cannot find object or property. 0x80092004 (-2146885628 CRYPT_E_NOT_FOUND)
So, what am I missing?
ISignerCertificate::Initialize requires that the private key be bound via the Requests or My store:
https://msdn.microsoft.com/en-us/library/windows/desktop/aa376832(v=vs.85).aspx:
If a private key is needed, only the personal and request stores are
searched.
If a private key is not needed, the root and intermediate CA
stores are also searched.
Windows expects that you only put the public portion of the CA into the CA (intermediate) or Root/ThirdPartyRoot stores, and that if you're the issuer you'll ALSO have it installed (with the private key now) into CurrentUser\My or LocalMachine\My.

Trying to create certificate request from existing public key (programmatically)

I am trying to create certificate request programmatically from existing public key. But I get an "The requested property value is empty. (Exception from HRESULT: 0x80094004)" exception.
Here is my code:
private static string CreateCertRequestMessage(string encodedPublicKeyInfo)
{
CObjectId objAlg = new CObjectId();
objAlg.InitializeFromAlgorithmName(
ObjectIdGroupId.XCN_CRYPT_PUBKEY_ALG_OID_GROUP_ID,
ObjectIdPublicKeyFlags.XCN_CRYPT_OID_INFO_PUBKEY_ANY,
AlgorithmFlags.AlgorithmFlagsNone,
"RSA");
CX509PublicKey objPublicKey = new CX509PublicKey();
objPublicKey.Initialize(objAlg, encodedPublicKeyInfo, "", EncodingType.XCN_CRYPT_STRING_HEX);
Console.WriteLine(objPublicKey.Algorithm.FriendlyName);
Console.WriteLine(objPublicKey.Algorithm.Value);
Console.WriteLine(objPublicKey.Length);
Console.WriteLine(objPublicKey.EncodedKey);
var objPkcs10 = new CX509CertificateRequestCertificate();
objPkcs10.InitializeFromPublicKey(
X509CertificateEnrollmentContext.ContextUser,
objPublicKey,
string.Empty);
var objExtensionKeyUsage = new CX509ExtensionKeyUsage();
objExtensionKeyUsage.InitializeEncode(
CERTENROLLLib.X509KeyUsageFlags.XCN_CERT_DIGITAL_SIGNATURE_KEY_USAGE |
CERTENROLLLib.X509KeyUsageFlags.XCN_CERT_NON_REPUDIATION_KEY_USAGE |
CERTENROLLLib.X509KeyUsageFlags.XCN_CERT_KEY_ENCIPHERMENT_KEY_USAGE |
CERTENROLLLib.X509KeyUsageFlags.XCN_CERT_DATA_ENCIPHERMENT_KEY_USAGE);
objPkcs10.X509Extensions.Add((CX509Extension)objExtensionKeyUsage);
var objObjectId = new CObjectId();
var objObjectIds = new CObjectIds();
var objX509ExtensionEnhancedKeyUsage = new CX509ExtensionEnhancedKeyUsage();
objObjectId.InitializeFromValue("1.3.6.1.5.5.7.3.2");
objObjectIds.Add(objObjectId);
objX509ExtensionEnhancedKeyUsage.InitializeEncode(objObjectIds);
objPkcs10.X509Extensions.Add((CX509Extension)objX509ExtensionEnhancedKeyUsage);
string templateName = "MHM Template";
CX509ExtensionTemplateName template = new CX509ExtensionTemplateName();
template.InitializeEncode(templateName);
objPkcs10.X509Extensions.Add((CX509Extension)template);
var objDN = new CX500DistinguishedName();
var subjectName = "CN = shaunxu.me, OU = ADCS, O = Blog, L = Beijng, S = Beijing, C = CN";
objDN.Encode(subjectName, X500NameFlags.XCN_CERT_NAME_STR_NONE);
objPkcs10.Subject = objDN;
CObjectId objHash = new CObjectId();
objHash.InitializeFromAlgorithmName(
ObjectIdGroupId.XCN_CRYPT_HASH_ALG_OID_GROUP_ID,
ObjectIdPublicKeyFlags.XCN_CRYPT_OID_INFO_PUBKEY_ANY,
AlgorithmFlags.AlgorithmFlagsNone,
"SHA1");
objPkcs10.HashAlgorithm = objHash;
var objEnroll = new CX509Enrollment();
objEnroll.InitializeFromRequest(objPkcs10);
var strRequest = objEnroll.CreateRequest(EncodingType.XCN_CRYPT_STRING_BASE64);
return strRequest;
}
I successfully create request from private key.
But I need to create request from public key.
Please help me. What exactly I am missing here.
Thanks in advance
The purpose of a public-key certificate is to confirm the ownership of the key pair (private and public key). That means, when I present a certificate with my name on it to another party, the certification authority confirms (by signing the certificate) that the key pair in fact belongs to me. In particular, it means that I "own" the private key. (Ownership in this context means that I know it but nobody else does.)
In order to confirm this, the certification authority must be sure that I am in possession of the private key. So, any certification process must, at some point, involve the private key. I will not give away the private key but at least I have to use it in a challenge/response exchange. The public key is used to verify the validity of my response and thus the certification authority knows that I am having the private key.
Look at it a different way. The public key is publicly known. What use would it be to have a certificate that confirms that I am the "owner" of the public key? Everybody knows it, you cannot "own" the public key.

How to generate CSR like it does IIS

I am working on integration with symantec api and use that code to generate CSR
private string GenerateCsr(string domain, string organization, string organizationUnit, string city, string state, string country) {
// Create all the objects that will be required
var objPkcs10 = new CX509CertificateRequestPkcs10();
var objPrivateKey = new CX509PrivateKey();
var objCSP = new CCspInformation();
var objCSPs = new CCspInformations();
var objDN = new CX500DistinguishedName();
var objEnroll = new CX509Enrollment();
var objObjectIds = new CObjectIds();
var objObjectId = new CObjectId();
var objExtensionKeyUsage = new CX509ExtensionKeyUsage();
var objX509ExtensionEnhancedKeyUsage = new CX509ExtensionEnhancedKeyUsage();
string strRequest;
try {
// Initialize the csp object using the desired Cryptograhic Service Provider (CSP)
objCSP.InitializeFromName(
"Microsoft RSA Schannel Cryptographic Provider"
);
// Add this CSP object to the CSP collection object
objCSPs.Add(
objCSP
);
// Provide key container name, key length and key spec to the private key object
//objPrivateKey.ContainerName = "AlejaCMa";
objPrivateKey.Length = 2048;
objPrivateKey.KeySpec = X509KeySpec.XCN_AT_SIGNATURE;
objPrivateKey.KeyUsage = X509PrivateKeyUsageFlags.XCN_NCRYPT_ALLOW_ALL_USAGES;
objPrivateKey.MachineContext = false;
// Provide the CSP collection object (in this case containing only 1 CSP object)
// to the private key object
objPrivateKey.CspInformations = objCSPs;
// Create the actual key pair
objPrivateKey.Create();
// Initialize the PKCS#10 certificate request object based on the private key.
// Using the context, indicate that this is a user certificate request and don't
// provide a template name
objPkcs10.InitializeFromPrivateKey(
X509CertificateEnrollmentContext.ContextUser,
objPrivateKey,
""
);
// Key Usage Extension
objExtensionKeyUsage.InitializeEncode(
X509KeyUsageFlags.XCN_CERT_DIGITAL_SIGNATURE_KEY_USAGE |
X509KeyUsageFlags.XCN_CERT_NON_REPUDIATION_KEY_USAGE |
X509KeyUsageFlags.XCN_CERT_KEY_ENCIPHERMENT_KEY_USAGE |
X509KeyUsageFlags.XCN_CERT_DATA_ENCIPHERMENT_KEY_USAGE
);
objPkcs10.X509Extensions.Add((CX509Extension)objExtensionKeyUsage);
// Enhanced Key Usage Extension
objObjectId.InitializeFromValue("1.3.6.1.5.5.7.3.2");
// OID for Client Authentication usage
objObjectIds.Add(objObjectId);
objX509ExtensionEnhancedKeyUsage.InitializeEncode(objObjectIds);
objPkcs10.X509Extensions.Add((CX509Extension)objX509ExtensionEnhancedKeyUsage);
// Encode the name in using the Distinguished Name object
objDN.Encode(
string.Format("CN={0}, O={1}, OU={2}, L={3}, S={4}, C={5}", domain, organization, organizationUnit, city, state, country),
X500NameFlags.XCN_CERT_NAME_STR_NONE
);
// Assing the subject name by using the Distinguished Name object initialized above
objPkcs10.Subject = objDN;
// Create enrollment request
objEnroll.InitializeFromRequest(objPkcs10);
strRequest = objEnroll.CreateRequest(
EncodingType.XCN_CRYPT_STRING_BASE64
);
return strRequest;
}
catch (Exception ex) {
throw new Exception("Can't generate CSR");
}
}
Symantec then returns base64 encoded certificate but I can't upload it to IIS. If I send CSR generated manually on IIS to symantec, I am able to upload returned certificate.
So, my question is how to generate CSR like it was generated on IIS.
It cannot be done the way you want it. Because the csr and private key generated are on one server, the signed certificate returned by the CA, you will need to have the private key that is generated when the CSR is created. But you are generating the private key on a different server and uploading the signed certificate given by Symantec on iis and IIS does not have the private key.
If it has to be done then you need to send the parameters directly to the Symantec API and then they will provide you with a PFX file which will be password protected and you can upload the pfx file on the IIS server.
I hope i answered you question.

Categories