The following code is used to request a certificate from an ADCS instance.
How do I extract the request, and the finalized pfx blob (and password protect it) for external storage?
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
// Add the CertEnroll namespace
using CERTENROLLLib;
using CERTCLIENTLib;
namespace CATest
{
public partial class Form1 : Form
{
private const int CC_DEFAULTCONFIG = 0;
private const int CC_UIPICKCONFIG = 0x1;
private const int CR_IN_BASE64 = 0x1;
private const int CR_IN_FORMATANY = 0;
private const int CR_IN_PKCS10 = 0x100;
private const int CR_DISP_ISSUED = 0x3;
private const int CR_DISP_UNDER_SUBMISSION = 0x5;
private const int CR_OUT_BASE64 = 0x1;
private const int CR_OUT_CHAIN = 0x100;
public Form1()
{
InitializeComponent();
}
// Create request
private void createRequestButton_Click(object sender, EventArgs e)
{
// Create all the objects that will be required
CX509CertificateRequestPkcs10 objPkcs10 = new CX509CertificateRequestPkcs10Class();
CX509PrivateKey objPrivateKey = new CX509PrivateKeyClass();
CCspInformation objCSP = new CCspInformationClass();
CCspInformations objCSPs = new CCspInformationsClass();
CX500DistinguishedName objDN = new CX500DistinguishedNameClass();
CX509Enrollment objEnroll = new CX509EnrollmentClass();
CObjectIds objObjectIds = new CObjectIdsClass();
CObjectId objObjectId = new CObjectIdClass();
CX509ExtensionKeyUsage objExtensionKeyUsage = new CX509ExtensionKeyUsageClass();
CX509ExtensionEnhancedKeyUsage objX509ExtensionEnhancedKeyUsage = new CX509ExtensionEnhancedKeyUsageClass();
string strRequest;
try
{
requestText.Text = "";
// Initialize the csp object using the desired Cryptograhic Service Provider (CSP)
objCSP.InitializeFromName(
"Microsoft Enhanced Cryptographic Provider v1.0"
);
// 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 = 1024;
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(
"CN=AlejaCMa",
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
);
requestText.Text = strRequest;
} catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
// Submit request to CA and get response
private void sendRequestButton_Click(object sender, EventArgs e)
{
// Create all the objects that will be required
CCertConfig objCertConfig = new CCertConfigClass();
CCertRequest objCertRequest = new CCertRequestClass();
string strCAConfig;
string strRequest;
int iDisposition;
string strDisposition;
string strCert;
try
{
strRequest = requestText.Text;
// Get CA config from UI
//strCAConfig = objCertConfig.GetConfig(CC_DEFAULTCONFIG);
strCAConfig = objCertConfig.GetConfig(CC_UIPICKCONFIG);
// Submit the request
iDisposition = objCertRequest.Submit(
CR_IN_BASE64 | CR_IN_FORMATANY,
strRequest,
null,
strCAConfig
);
// Check the submission status
if (CR_DISP_ISSUED != iDisposition) // Not enrolled
{
strDisposition = objCertRequest.GetDispositionMessage();
if (CR_DISP_UNDER_SUBMISSION == iDisposition) // Pending
{
MessageBox.Show("The submission is pending: " + strDisposition);
return;
}
else // Failed
{
MessageBox.Show("The submission failed: " + strDisposition);
MessageBox.Show("Last status: " + objCertRequest.GetLastStatus().ToString());
return;
}
}
// Get the certificate
strCert = objCertRequest.GetCertificate(
CR_OUT_BASE64 | CR_OUT_CHAIN
);
responseText.Text = strCert;
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
// Install response from CA
private void acceptPKCS7Button_Click(object sender, EventArgs e)
{
// Create all the objects that will be required
CX509Enrollment objEnroll = new CX509EnrollmentClass();
string strCert;
try
{
strCert = responseText.Text;
// Install the certificate
objEnroll.Initialize(X509CertificateEnrollmentContext.ContextUser);
objEnroll.InstallResponse(
InstallResponseRestrictionFlags.AllowUntrustedRoot,
strCert,
EncodingType.XCN_CRYPT_STRING_BASE64,
null
);
MessageBox.Show("Certificate installed!");
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
}
}
The problem with the code is that I don't want to store crypto material in the Windows DPAPI, and in particular I don't want to "install" the certificate, since the crypto material needs to be securely sent and shared among multiple computers.
The particular lines of code I'm having trouble with include this:
// Install the certificate
objEnroll.Initialize(X509CertificateEnrollmentContext.ContextUser);
objEnroll.InstallResponse(
InstallResponseRestrictionFlags.AllowUntrustedRoot,
strCert,
EncodingType.XCN_CRYPT_STRING_BASE64,
null
);
I also want to save and import the certificate request from a custom store... again not DPAPI
You can call the CreatePFX method on the objEnroll instance in you acceptPKCS7Button_Click method:
objEnroll.CreatePFX("S3cret!", PFXExportOptions.PFXExportChainWithRoot, EncodingType.XCN_CRYPT_STRING_BASE64);
Related
Using the CA web page https://{CAServerName}/certsrv/certrqma.asp, I can manually create a request based on the template and install the certificate after approval.
How to do the same but in c#?
This is my first time working with certificates and this is what I got.
While searching, I found 2 useful articles:
How to work with certreq
How to create a certificate request with CertEnroll and .NET (C#)
In the first case, I understood how to form a request.
Here is an example:
req.inf
[NewRequest]
Subject = ""
KeyLength = 2048
RequestType = PKCS10
ProviderName = "Microsoft Enhanced Cryptographic Provider v1.0"
[RequestAttributes]
CertificateTemplate = ""
Subject - Certificate-Details-Subject.
CertificateTemplate - get frendly name from Certificate-Details-Subject, search TemplatePropFriendlyName in "certutil.exe -Template" and get TemplatePropCommonName.
certreq -q -new req.inf req.req
certreq -config "CAHostName.domain.local\CAName" -submit "req.req" "cert.cer"
certreq -accept "cert.cer"
In the second case, I took example and reduced it a bit.
public class TestCertManager
{
private const int CC_DEFAULTCONFIG = 0;
private const int CC_UIPICKCONFIG = 0x1;
private const int CR_IN_BASE64 = 0x1;
private const int CR_IN_FORMATANY = 0;
private const int CR_IN_PKCS10 = 0x100;
private const int CR_DISP_ISSUED = 0x3;
private const int CR_DISP_UNDER_SUBMISSION = 0x5;
private const int CR_OUT_BASE64 = 0x1;
private const int CR_OUT_CHAIN = 0x100;
// Create request
public string CreateRequest()
{
string subject = "<subject>";
string templateName = "<templateName>";
// Create all the objects that will be required
CX509CertificateRequestPkcs10 objPkcs10 = new CX509CertificateRequestPkcs10();
CX509PrivateKey objPrivateKey = new CX509PrivateKey();
CCspInformation objCSP = new CCspInformation();
CCspInformations objCSPs = new CCspInformations();
CX500DistinguishedName objDN = new CX500DistinguishedName();
CX509Enrollment objEnroll = new CX509Enrollment();
// Initialize the csp object using the desired Cryptograhic Service Provider (CSP)
objCSP.InitializeFromName("Microsoft Enhanced Cryptographic Provider v1.0");
// Add this CSP object to the CSP collection object
objCSPs.Add(objCSP);
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();
// provide a template name
objPkcs10.InitializeFromPrivateKey(X509CertificateEnrollmentContext.ContextUser,objPrivateKey, templateName);
// Encode the name in using the Distinguished Name object
objDN.Encode(
subject,
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);
string strRequest = objEnroll.CreateRequest(EncodingType.XCN_CRYPT_STRING_BASE64);
return strRequest;
}
// Submit request to CA and get response
public string SendRequest(string strRequest)
{
// Create all the objects that will be required
CCertConfig objCertConfig = new CCertConfig();
CCertRequest objCertRequest = new CCertRequest();
string strCAConfig = #"CAHostName\CAName";
int iDisposition;
string strDisposition;
string strCert;
// Submit the request
iDisposition = objCertRequest.Submit(CR_IN_BASE64 | CR_IN_FORMATANY, strRequest, null, strCAConfig);
// Check the submission status
if (CR_DISP_ISSUED != iDisposition) // Not enrolled
{
strDisposition = objCertRequest.GetDispositionMessage();
if (CR_DISP_UNDER_SUBMISSION == iDisposition) // Pending
{
Console.WriteLine("The submission is pending: " + strDisposition);
return "";
}
else // Failed
{
Console.WriteLine("The submission failed: " + strDisposition);
Console.WriteLine("Last status: " + objCertRequest.GetLastStatus().ToString());
return "";
}
}
// Get the certificate
strCert = objCertRequest.GetCertificate(CR_OUT_BASE64 | CR_OUT_CHAIN);
return strCert;
}
// Install response from CA
public void AcceptPKCS7(string strCert)
{
// Create all the objects that will be required
CX509Enrollment objEnroll = new CX509Enrollment();
// Install the certificate
objEnroll.Initialize(X509CertificateEnrollmentContext.ContextUser);
objEnroll.InstallResponse(
InstallResponseRestrictionFlags.AllowUntrustedRoot,
strCert,
EncodingType.XCN_CRYPT_STRING_BASE64,
null
);
Console.WriteLine("Certificate installed!");
}
}
I'm still looking for the best solution, but at least I have 2 working options.
I have a problem when I try to decrypt a string generated using RSA in C#. It Encrypts the strings well, but it throws an error when I try to decrypt the string using the private key:
Error occurred while decoding OAEP padding.
I tried changing the fOAEP parameter between true and false, changing the RSACryptoServiceProviders with 2048 as parameter and still doesn't work.
Project is a WPF App which generates 2 files with the keys, keys are loaded by the core.cs file.
Then keys are being loaded.
The example I taken only uses the public key to encrypt the string and only the private key to decrypt the string.
Core.cs File
// Keys are generated
public void GeneratePublicKey(string publicKeyFile) {
using (var rsa = new RSACryptoServiceProvider(2048)) {
rsa.PersistKeyInCsp = false;
if (File.Exists(publicKeyFile))
File.Delete(publicKeyFile);
//and the public key ...
var pubKey = rsa.ExportParameters(false);
//converting the public key into a string representation
string pubKeyString; {
//we need some buffer
var sw = new System.IO.StringWriter();
//we need a serializer
var xs = new System.Xml.Serialization.XmlSerializer(typeof(RSAParameters));
//serialize the key into the stream
xs.Serialize(sw, pubKey);
//get the string from the stream
pubKeyString = sw.ToString();
}
File.WriteAllText(publicKeyFile, pubKeyString);
}
}
public void GeneratePrivateKey(string privateKeyFile) {
using (var rsa = new RSACryptoServiceProvider(2048)) {
rsa.PersistKeyInCsp = false;
if (File.Exists(privateKeyFile))
File.Delete(privateKeyFile);
//how to get the private key
var privKey = rsa.ExportParameters(true);
//converting the public key into a string representation
string privKeyString;
{
//we need some buffer
var sw = new System.IO.StringWriter();
//we need a serializer
var xs = new System.Xml.Serialization.XmlSerializer(typeof(RSAParameters));
//serialize the key into the stream
xs.Serialize(sw, privKey);
//get the string from the stream
privKeyString = sw.ToString();
}
File.WriteAllText(privateKeyFile, privKeyString);
}
}
//Si las llaves ya existen entonces NO Generar, solo leer la llave indicada (leer el texto de la ruta)
public RSAParameters ReadPublicKey(string publicKeyFile) {
//Leer
string pubKeyString = File.ReadAllText(publicKeyFile);
//Reconvertir
var sr = new System.IO.StringReader(pubKeyString);
//we need a deserializer
var xs = new System.Xml.Serialization.XmlSerializer(typeof(RSAParameters));
//get the object back from the stream
return (RSAParameters)xs.Deserialize(sr);
}
public RSAParameters ReadPrivateKey(string privateKeyFile) {
//Leer
string privKeyString = File.ReadAllText(privateKeyFile);
//Reconvertir
var sr = new System.IO.StringReader(privKeyString);
//we need a deserializer
var xs = new System.Xml.Serialization.XmlSerializer(typeof(RSAParameters));
//get the object back from the stream
return (RSAParameters)xs.Deserialize(sr);
}
//Con la llave publica se encripta el texto
public string Encrypt(string publicKeyFile, string textToEncrypt) {
var csp = new RSACryptoServiceProvider();
csp.ImportParameters(ReadPublicKey(publicKeyFile));
//for encryption, always handle bytes...
var bytesPlainTextData = System.Text.Encoding.Unicode.GetBytes(textToEncrypt);
//apply pkcs#1.5 padding and encrypt our data
var bytesCypherText = csp.Encrypt(bytesPlainTextData, true);
//we might want a string representation of our cypher text... base64 will do
Debug.WriteLine("Texto Encriptado: "+Convert.ToBase64String(bytesCypherText));
return Convert.ToBase64String(bytesCypherText);
}
/// <summary>
/// Con la llave Privada se Desencripta
/// </summary>
/// <param name="privateKeyFile"></param>
/// <param name="textToDecrypt"></param>
/// <returns></returns>
public string Decrypt(string privateKeyFile, string textToDecrypt) {
//first, get our bytes back from the base64 string ...
var bytesCypherText = Convert.FromBase64String(textToDecrypt);
//we want to decrypt, therefore we need a csp and load our private key
var csp = new RSACryptoServiceProvider();
csp.ImportParameters(ReadPrivateKey(privateKeyFile));
//decrypt and strip pkcs#1.5 padding
var bytesPlainTextData = csp.Decrypt(bytesCypherText, true);
Debug.WriteLine("Desencriptado: "+
System.Text.Encoding.Unicode.GetString(bytesPlainTextData));
//get our original plainText back...
return System.Text.Encoding.Unicode.GetString(bytesPlainTextData);
}
MainWindow.cs
public partial class MainWindow : Window {
readonly RsaEnc _rs = new RsaEnc();
private string _publicKeyFile = "./public.cert";
private string _privateKeyFile = "./private.key";
public MainWindow() {
InitializeComponent();
}
private void GenerateBtn_Click(object sender, RoutedEventArgs e) {
_rs.GeneratePublicKey(_publicKeyFile);
_rs.GeneratePrivateKey(_privateKeyFile);
}
private void OpenPublic_Click(object sender, RoutedEventArgs e) {
OpenFileDialog fileDialog = new OpenFileDialog {
Multiselect = false, Filter = "Public |*.cert", DefaultExt = ".cert"
};
bool? dialogOk = fileDialog.ShowDialog();
if (dialogOk == true) {
string sFilenames = "";
foreach (string sFileName in fileDialog.FileNames) {
sFilenames += ";" + sFileName;
}
sFilenames = sFilenames.Substring(1);
_publicKeyFile = sFilenames;//Esto solo da la RUTA
Debug.WriteLine("public Cert: " + _publicKeyFile);
}
}
private void OpenPrivate_Click(object sender, RoutedEventArgs e) {
OpenFileDialog fileDialog = new OpenFileDialog
{
Multiselect = false, Filter = "Certificates |*.key", DefaultExt = ".key"
};
bool? dialogOk = fileDialog.ShowDialog();
if (dialogOk == true) {
string sFilenames = "";
foreach (string sFileName in fileDialog.FileNames) {
sFilenames += ";" + sFileName;
}
sFilenames = sFilenames.Substring(1);
_privateKeyFile = sFilenames; //Esto solo da la RUTA
Debug.WriteLine("private Key: " + _privateKeyFile);
}
}
private void EncryptBtn_Click(object sender, RoutedEventArgs e) {
_rs.Encrypt(_publicKeyFile, BoxToEncrypt.Text);
}
private void DecryptBtn_Click(object sender, RoutedEventArgs e) {
_rs.Decrypt(_privateKeyFile, BoxToDecrypt.Text);
}
}
First thing, WPF and Decryption are two different things. So, we have to concentrate only on the RSA encrypt/decrypt part.
In your code, you are trying to generate public/private keys using different methods. One single rsaprovider should export both parameters. I have provided a sample code for using RSA encryption/decryption. Please check below.
internal sealed class RSA
{
public static (string public_key, string private_key) getKeyPair()
{
try
{
var rsa_provider = new RSACryptoServiceProvider(1024);
return (rsa_provider.ToXmlString(false), rsa_provider.ToXmlString(true));
}
catch (Exception ex)
{
throw ex;
}
}
public static byte[] shroud_divulge(byte[] input_byte, string _key,bool is_shroud)
{
try
{
var rsa_provider = new RSACryptoServiceProvider();
rsa_provider.FromXmlString(_key);
var padding = RSAEncryptionPadding.OaepSHA256;
switch(is_shroud)
{
case true:
return rsa_provider.Encrypt(input_byte, padding);
case false:
return rsa_provider.Decrypt(input_byte, padding);
}
return null;
}
catch (Exception)
{
throw;
}
}
}
It has two simple methods and returns output using valuetuple(So add Valutuple nuget package).
The file saving and processing are done outside of the RSA logic. Shroud- Encryption, Divulge-Decryption. (Please excuse the naming conventions used, its as per our company standards).
We usually ship our publickey along with our WPF application.
For encryption (Shroud), we pass in the private key..
For decryption (Divulge) , we pass in the publich key..
RSA keys are always generated in pairs, a public and a private. These keys are created in the constructor of RSACryptoServiceProvider. Your code calls this constructor in both GeneratePublicKey and GeneratePrivateKey, therefore there will be two unrelated key-pairs generated.
You should be able to check this. When exporting parameters the public component is always included. If they are not identical that that would serve as confirmation.
The fix would be to generate the public and private key at the same time.
Is there a simple way to check if a given AsymmetricAlgorithm is a private or a public key? Consider the following example:
private void SavePrivateKey(AsymmetricAlgorithm asymmetricAlgorithm)
{
// if (asymmetricAlgorithm.IsPrivateKey == false)
// throw new ArgumentException();
}
private void SavePrivateKeys()
{
var certificate = CreateCertificate();
var privateKey = RSACertificateExtensions.GetRSAPrivateKey(certificate);
var publicKey = RSACertificateExtensions.GetRSAPublicKey(certificate);
SavePrivateKey(privateKey);
SavePrivateKey(publicKey); // this should throw an exception
}
private X509Certificate2 CreateCertificate()
{
CngKeyCreationParameters keyParams = new CngKeyCreationParameters();
keyParams.KeyUsage = CngKeyUsages.Signing;
keyParams.Provider = CngProvider.MicrosoftSoftwareKeyStorageProvider;
keyParams.ExportPolicy = CngExportPolicies.AllowExport | CngExportPolicies.AllowPlaintextExport;
keyParams.Parameters.Add(new CngProperty("Length", BitConverter.GetBytes(2048), CngPropertyOptions.None));
var cngKey = CngKey.Create(CngAlgorithm.Rsa, Guid.NewGuid().ToString(), keyParams);
var rsaKey = new RSACng(cngKey);
var req = new CertificateRequest("cn=mycert", rsaKey, HashAlgorithmName.SHA256, RSASignaturePadding.Pss);
var cert = req.CreateSelfSigned(DateTimeOffset.Now, DateTimeOffset.Now.AddYears(5));
return cert;
}
Here, both private and public keys have the same type - RSACng. In theory I can try to export key parameters and see if the public key fails to export the private key params. But then I would not know if the export failed because it is a public key, or because it is missing export policies or something else went wrong. Also the underlying key type may be different, it could be RSACng, RSACryptoServiceProvider, DSA etc.
The ToXmlString() method takes in the includePrivateParameters parameter. If that parameter is set, and the AsymmetricAlgorithm object does not include information about the private key, ToXmlString() will throw a CryptographicException exception:
private void SavePrivateKey(AsymmetricAlgorithm aa)
{
System.Console.Write("This is ");
try
{
aa.ToXmlString(true);
}
catch(CryptographicException ce)
{
System.Console.Write("not ");
}
System.Console.WriteLine("a private key");
}
Scenario:
I am learning AWS CloudHSM. So far, I have
created an EC2 instance with Windows Server 2019 Datacenter as OS
created a certification authority (root CA) on this server with Dintinguised Name "CN=myservername-CA1" (https://docs.aws.amazon.com/cloudhsm/latest/userguide/win-ca-setup.html )
while connected to EC2 instance via RDP, I can login to my cloud hsm account and can manage users, create new keys etc.
Details of CA:
Provider: RSA#Cavium Key Storage Provider
Key Length: 2048
Hash Algorithm: SHA256
Distinguished Name: CN=myservername-CA1
Cert Database log: C:\Windows\system32\CertLog
Now, I have developed a sample .Net WebAPI application which should send a CSR request to my CA and CA should return the signed certificate to the requester. This application is hosted as a web app on IIS on the same EC2 instance.
Source Code (https://blogs.msdn.microsoft.com/alejacma/2008/09/05/how-to-create-a-certificate-request-with-certenroll-and-net-c/ ):
using CloudHsmDemo.Models;
using System;
using System.Threading.Tasks;
using CERTENROLLLib;
using CERTCLILib;
namespace CloudHsmDemo.Services
{
public interface ICertificateService
{
Task<CertificateSigningResponse> SignAsync(CertificateSigningRequest csr);
}
public class CertificateService : ICertificateService
{
private const int CC_DEFAULTCONFIG = 0;
private const int CC_UIPICKCONFIG = 0x1;
private const int CR_IN_BASE64 = 0x1;
private const int CR_IN_FORMATANY = 0;
private const int CR_IN_PKCS10 = 0x100;
private const int CR_DISP_ISSUED = 0x3;
private const int CR_DISP_UNDER_SUBMISSION = 0x5;
private const int CR_OUT_BASE64 = 0x1;
private const int CR_OUT_CHAIN = 0x100;
public async Task<CertificateSigningResponse> SignAsync(CertificateSigningRequest csr)
{
if (csr.ShouldReturnDummyData)
{
return await DummySigningAsync(csr);
}
else
{
return await ActualSigningAsync(csr);
}
}
private async Task<CertificateSigningResponse> DummySigningAsync(CertificateSigningRequest csr)
{
return PopulateCertificateSigningResponse("Sample Certificate", "Sample Message");
}
private async Task<CertificateSigningResponse> ActualSigningAsync(CertificateSigningRequest csr)
{
// Create all the objects that will be required
CCertConfig objCertConfig = new CCertConfigClass();
CCertRequest objCertRequest = new CCertRequestClass();
// string strCAConfig;
string strRequest;
int iDisposition;
string strDisposition;
string strCert;
CertificateSigningResponse certificateSigningResponse;
try
{
strRequest = await CreateCertificateSigningRequest(csr);
// Get CA config from UI
// strCAConfig = objCertConfig.GetConfig(CC_DEFAULTCONFIG);
//strCAConfig = objCertConfig.GetConfig(CC_UIPICKCONFIG);
// Submit the request
iDisposition = objCertRequest.Submit(
CR_IN_BASE64 | CR_IN_FORMATANY,
strRequest,
null,
"<my_ec2_instance_public_dns>\\<my_server_name>"
);
// Check the submission status
if (CR_DISP_ISSUED != iDisposition) // Not enrolled
{
strDisposition = objCertRequest.GetDispositionMessage();
if (CR_DISP_UNDER_SUBMISSION == iDisposition) // Pending
{
certificateSigningResponse = PopulateCertificateSigningResponse(string.Empty, $"The submission is pending: {strDisposition}");
}
else // Failed
{
certificateSigningResponse = PopulateCertificateSigningResponse(string.Empty, $"The submission failed: {strDisposition}; Last Status: {objCertRequest.GetLastStatus().ToString()}");
}
}
// Get the certificate
strCert = objCertRequest.GetCertificate(
CR_OUT_BASE64 | CR_OUT_CHAIN
);
certificateSigningResponse = PopulateCertificateSigningResponse(strCert, "Certificate signing process succeeded.");
}
catch (Exception ex)
{
certificateSigningResponse = PopulateCertificateSigningResponse(string.Empty, ex.Message);
}
if (certificateSigningResponse == null)
{
certificateSigningResponse = PopulateCertificateSigningResponse(string.Empty, "Certificate signing process failed.");
}
return certificateSigningResponse;
}
// this method creates a request string properly when
private async Task<string> CreateCertificateSigningRequest(CertificateSigningRequest csr)
{
// Create all the objects that will be required
CX509CertificateRequestPkcs10 objPkcs10 = new CX509CertificateRequestPkcs10Class();
CX509PrivateKey objPrivateKey = new CX509PrivateKeyClass();
CCspInformation objCSP = new CCspInformationClass();
CCspInformations objCSPs = new CCspInformationsClass();
CX500DistinguishedName objDN = new CX500DistinguishedNameClass();
CX509Enrollment objEnroll = new CX509EnrollmentClass();
CObjectIds objObjectIds = new CObjectIdsClass();
CObjectId objObjectId = new CObjectIdClass();
CX509ExtensionKeyUsage objExtensionKeyUsage = new CX509ExtensionKeyUsageClass();
CX509ExtensionEnhancedKeyUsage objX509ExtensionEnhancedKeyUsage = new CX509ExtensionEnhancedKeyUsageClass();
string strRequest;
try
{
// Initialize the csp object using the desired Cryptograhic Service Provider (CSP)
objCSP.InitializeFromName("Microsoft Enhanced Cryptographic Provider v1.0");
//objCSP.InitializeFromName("Cavium Key Storage 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.Length = csr.KeySize;
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("CN=<myservername>-CA1", 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
);
}
catch (Exception ex)
{
throw ex;
}
return strRequest;
}
private CertificateSigningResponse PopulateCertificateSigningResponse(string certificate, string message)
{
var responseObject = new CertificateSigningResponse
{
Certificate = certificate,
Message = message,
DateTimeInUTC = DateTime.UtcNow,
Status = string.IsNullOrWhiteSpace(certificate) == true ? "Fail" : "Success"
};
return responseObject;
}
}
}
My sample JSON request:
{
"CommonName":"My Test CSR",
"Organization":"My Office",
"OrganizationalUnit":"My Department",
"CityOrLocality":"Sydney",
"StateOrProvince":"NSW",
"CountryOrRegion":"AU",
"KeySize":2048,
"ShouldReturnDummyData": false
}
Problem(s):
when "Cavium Key Storage Provider" or "RSA#Cavium Key Storage
Provider" is used to initialize objCSP, "Invalid provider
specified. (Exception from HRESULT: 0x80090013)" exception is thrown
when "Microsoft Enhanced Cryptographic Provider v1.0" is used to initialize objCSP, "CCertRequest::Submit: The RPC server is
unavailable. 0x800706ba" exception is thrown
To resolve the "The RPC server is unavailable" issue, I have followed the steps https://itworldjd.wordpress.com/2015/10/21/pki-certificates-troubleshooting-certificate-enrollment-rpc-server-is-unavailable/ but no luck.
I hit this error too. Hopefully someone can benefit from how I addressed it.
After requesting a new cert over web enrollment I also got the error.
CCertRequest::Submit: The RPC server is unavailable. 0x800706ba (WIN32: 1722 RPC_S_SERVER_UNAVAILABLE)
Without going into all the detail of DCOM permissions you need to ensure you are accessing the Certificate web server remotely and not locally from the CA server.
I have the following controller:
[Authorize]
public class SetupController : ApiController
{
[HttpPost]
public Task async SetupPartnerPackAsync(SetupInformation info)
{
if (info.SslCertificateGenerate)
{
SetupManager.CreateX509Certificate(info);
}
else
{
SetupManager.LoadX509Certificate(info);
}
info.SslCertificateThumbprint = SetupManager.GetX509CertificateThumbprint(info);
info.AzureAppKeyCredential = SetupManager.GetX509CertificateInformation(info);
await SetupManager.RegisterAzureADApplication(info);
}
}
But I have the following 2 error which seems simple:
Severity Code Description Project File Line Suppression State
Error CS1520 Method must have a return
type InnovationInABoxWebApi H:\InnovationInABoxWebApi\InnovationInABoxWebApi\Controllers\SetupController.cs 24 Active
Severity Code Description Project File Line Suppression State
Error CS4033 The 'await' operator can only be used within an async
method. Consider marking this method with the 'async' modifier and
changing its return type to
'Task'. InnovationInABoxWebApi H:\InnovationInABoxWebApi\InnovationInABoxWebApi\Controllers\SetupController.cs 39 Active
However I am not sure how to fix this, as the operation can take some time to complete, it really needs to be asybnc
and the setupmanager
using CERTENROLLLib;
using Microsoft.Identity.Client;
using Microsoft.Online.SharePoint.TenantAdministration;
using Microsoft.SharePoint.Client;
using Newtonsoft.Json;
using OfficeDevPnP.Core;
using OfficeDevPnP.Core.Entities;
using OfficeDevPnP.Core.Framework.Provisioning.Model;
using OfficeDevPnP.Core.Framework.Provisioning.ObjectHandlers;
using OfficeDevPnP.Core.Framework.Provisioning.Providers.Xml;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Configuration;
using System.IO;
using System.Linq;
using System.Resources;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Web;
using System.Xml.Linq;
namespace InnovationInABoxWebApi.Components
{
public static class SetupManager
{
public static String GetX509CertificateThumbprint(SetupInformation info)
{
var certificate = info.AuthenticationCertificate;
return (certificate.Thumbprint.ToUpper());
}
public static String GetX509CertificateInformation(SetupInformation info)
{
// var basePath = String.Format(#"{0}..\..\..\..\Scripts\", AppDomain.CurrentDomain.BaseDirectory);
var certificate = info.AuthenticationCertificate;
//var certificate = new X509Certificate2();
//if (info.SslCertificateGenerate)
//{
// certificate.Import($#"{basePath}{info.SslCertificateCommonName}.cer");
//}
//else
//{
// certificate = new X509Certificate2(info.SslCertificateFile, info.SslCertificatePassword);
//}
var rawCert = certificate.GetRawCertData();
var base64Cert = System.Convert.ToBase64String(rawCert);
var rawCertHash = certificate.GetCertHash();
var base64CertHash = System.Convert.ToBase64String(rawCertHash);
var KeyId = System.Guid.NewGuid().ToString();
var keyCredential =
"{" +
"\"customKeyIdentifier\": \"" + base64CertHash + "\"," +
"\"keyId\": \"" + KeyId + "\"," +
"\"type\": \"AsymmetricX509Cert\"," +
"\"usage\": \"Verify\"," +
"\"key\": \"" + base64Cert + "\"" +
"}";
return (keyCredential);
}
public static void CreateX509Certificate(SetupInformation info)
{
var certificate = CreateSelfSignedCertificate(info.SslCertificateCommonName.ToLower(),
info.SslCertificateStartDate, info.SslCertificateEndDate, info.SslCertificatePassword);
SaveCertificateFiles(info, certificate);
}
public static void LoadX509Certificate(SetupInformation info)
{
var certificate = new X509Certificate2(info.SslCertificateFile, info.SslCertificatePassword);
info.AuthenticationCertificate = certificate;
info.SslCertificateCommonName = certificate.SubjectName.Name;
}
public static void SaveCertificateFiles(SetupInformation info, X509Certificate2 certificate)
{
info.AuthenticationCertificate = certificate;
//var basePath = String.Format(#"{0}..\..\..\..\Scripts\", AppDomain.CurrentDomain.BaseDirectory);
//info.SslCertificateFile = $#"{basePath}{info.SslCertificateCommonName}.pfx";
//var pfx = certificate.Export(X509ContentType.Pfx, info.SslCertificatePassword);
//System.IO.File.WriteAllBytes(info.SslCertificateFile, pfx);
//var cer = certificate.Export(X509ContentType.Cert);
//System.IO.File.WriteAllBytes($#"{basePath}{info.SslCertificateCommonName}.cer", cer);
}
public static X509Certificate2 CreateSelfSignedCertificate(string subjectName, DateTime startDate, DateTime endDate, String password)
{
// Create DistinguishedName for subject and issuer
var name = new CX500DistinguishedName();
name.Encode("CN=" + subjectName, X500NameFlags.XCN_CERT_NAME_STR_NONE);
// Create a new Private Key for the certificate
CX509PrivateKey privateKey = new CX509PrivateKey();
privateKey.ProviderName = "Microsoft RSA SChannel Cryptographic Provider";
privateKey.KeySpec = X509KeySpec.XCN_AT_KEYEXCHANGE;
privateKey.Length = 2048;
privateKey.SecurityDescriptor = "D:PAI(A;;0xd01f01ff;;;SY)(A;;0xd01f01ff;;;BA)(A;;0x80120089;;;NS)";
privateKey.MachineContext = true;
privateKey.ExportPolicy = X509PrivateKeyExportFlags.XCN_NCRYPT_ALLOW_EXPORT_FLAG;
privateKey.Create();
// Define the hashing algorithm
var serverauthoid = new CObjectId();
serverauthoid.InitializeFromValue("1.3.6.1.5.5.7.3.1"); // Server Authentication
var ekuoids = new CObjectIds();
ekuoids.Add(serverauthoid);
var ekuext = new CX509ExtensionEnhancedKeyUsage();
ekuext.InitializeEncode(ekuoids);
// Create the self signing request
var cert = new CX509CertificateRequestCertificate();
cert.InitializeFromPrivateKey(X509CertificateEnrollmentContext.ContextMachine, privateKey, String.Empty);
cert.Subject = name;
cert.Issuer = cert.Subject;
cert.NotBefore = startDate;
cert.NotAfter = endDate;
cert.X509Extensions.Add((CX509Extension)ekuext);
cert.Encode();
// Enroll the certificate
var enroll = new CX509Enrollment();
enroll.InitializeFromRequest(cert);
string certData = enroll.CreateRequest(EncodingType.XCN_CRYPT_STRING_BASE64HEADER);
enroll.InstallResponse(InstallResponseRestrictionFlags.AllowUntrustedCertificate,
certData, EncodingType.XCN_CRYPT_STRING_BASE64HEADER, String.Empty);
var base64encoded = enroll.CreatePFX(password, PFXExportOptions.PFXExportChainWithRoot);
// Instantiate the target class with the PKCS#12 data
return new X509Certificate2(
System.Convert.FromBase64String(base64encoded), password,
System.Security.Cryptography.X509Certificates.X509KeyStorageFlags.Exportable);
}
public async static Task RegisterAzureADApplication(SetupInformation info)
{
// Fix the App URL
if (!info.AzureWebAppUrl.EndsWith("/"))
{
info.AzureWebAppUrl = info.AzureWebAppUrl + "/";
}
// Load the App Manifest template
//Stream stream = typeof(SetupManager)
// .Assembly
// .GetManifestResourceStream("OfficeDevPnP.PartnerPack.Setup.Resources.azure-ad-app-manifest.json");
using (StreamReader sr = new StreamReader("Resources\azure-ad-app-manifest.json"))
{
// Get the JSON manifest
var jsonApplication = sr.ReadToEnd();
var application = JsonConvert.DeserializeObject<AzureAdApplication>(jsonApplication);
var keyCredential = JsonConvert.DeserializeObject<KeyCredential>(info.AzureAppKeyCredential);
application.displayName = info.ApplicationName;
application.homepage = info.AzureWebAppUrl;
application.identifierUris = new List<String>();
application.identifierUris.Add(info.ApplicationUniqueUri);
application.keyCredentials = new List<KeyCredential>();
application.keyCredentials.Add(keyCredential);
application.replyUrls = new List<String>();
application.replyUrls.Add(info.AzureWebAppUrl);
// Generate the Application Shared Secret
var startDate = DateTime.Now;
Byte[] bytes = new Byte[32];
using (var rand = System.Security.Cryptography.RandomNumberGenerator.Create())
{
rand.GetBytes(bytes);
}
info.AzureAppSharedSecret = System.Convert.ToBase64String(bytes);
application.passwordCredentials = new List<object>();
application.passwordCredentials.Add(new AzureAdApplicationPasswordCredential
{
CustomKeyIdentifier = null,
StartDate = startDate.ToString("o"),
EndDate = startDate.AddYears(2).ToString("o"),
KeyId = Guid.NewGuid().ToString(),
Value = info.AzureAppSharedSecret,
});
// Get an Access Token to create the application via Microsoft Graph
var office365AzureADAccessToken = await AzureManagementUtility.GetAccessTokenSilentAsync(
AzureManagementUtility.MicrosoftGraphResourceId,
ConfigurationManager.AppSettings["O365:ClientId"]);
var azureAdApplicationCreated = false;
// Create the Azure AD Application
try
{
await CreateAzureADApplication(info, application, office365AzureADAccessToken);
azureAdApplicationCreated = true;
}
catch (ApplicationException ex)
{
var graphError = JsonConvert.DeserializeObject<GraphError>(((HttpException)ex.InnerException).Message);
if (graphError != null && graphError.error.code == "Request_BadRequest" &&
graphError.error.message.Contains("identifierUris already exists"))
{
// We need to remove the existing application
// Thus, retrieve it
String jsonApplications = await HttpHelper.MakeGetRequestForStringAsync(
String.Format("{0}applications?$filter=identifierUris/any(c:c+eq+'{1}')",
AzureManagementUtility.MicrosoftGraphBetaBaseUri,
HttpUtility.UrlEncode(info.ApplicationUniqueUri)),
office365AzureADAccessToken);
var applications = JsonConvert.DeserializeObject<AzureAdApplications>(jsonApplications);
var applicationToUpdate = applications.Applications.FirstOrDefault();
if (applicationToUpdate != null)
{
// Remove it
await HttpHelper.MakeDeleteRequestAsync(
String.Format("{0}applications/{1}",
AzureManagementUtility.MicrosoftGraphBetaBaseUri,
applicationToUpdate.Id),
office365AzureADAccessToken);
// And add it again
await CreateAzureADApplication(info, application, office365AzureADAccessToken);
azureAdApplicationCreated = true;
}
}
}
if (azureAdApplicationCreated)
{
// TODO: We should upload the logo
// property mainLogo: stream of the application via PATCH
}
}
}
public static async Task CreateAzureADApplication(SetupInformation info, AzureAdApplication application, string office365AzureADAccessToken)
{
String jsonResponse = await HttpHelper.MakePostRequestForStringAsync(
String.Format("{0}applications",
AzureManagementUtility.MicrosoftGraphBetaBaseUri),
application,
"application/json", office365AzureADAccessToken);
var azureAdApplication = JsonConvert.DeserializeObject<AzureAdApplication>(jsonResponse);
info.AzureAppClientId = azureAdApplication.AppId.HasValue ? azureAdApplication.AppId.Value : Guid.Empty;
}
}
}
You are defining the method with async word after the return type Task, async must be before Task.
public async Task SetupPartnerPackAsync(SetupInformation info)
{
.
.
.