Fail to send/receive Client Certificate from Console App to web api - c#

I created a Web Api to accept a client certificate.
I am calling the web api from a console app using the following code.
var uri = new Uri("https://myservice.azurewebsites.net/api/values");
var handler = new WebRequestHandler();
handler.ClientCertificateOptions = ClientCertificateOption.Manual;
var certResults = new X509Store(StoreLocation.LocalMachine);
var certStore = new X509Store(StoreName.My, StoreLocation.CurrentUser);
try
{
certStore.Open(OpenFlags.ReadOnly);
}
catch (Exception)
{
Console.WriteLine("Unable to access Certificate store");
}
var thumbprint = "<<self signed cert thumbprint>>";
var certCollection = certStore.Certificates.Find(X509FindType.FindByThumbprint, thumbprint, false);
certStore.Close();
HttpClient client = new HttpClient(handler);
if (certCollection.Count > 0)
{
handler.ClientCertificates.Add(certCollection[0]);
client.DefaultRequestHeaders.Add("Thumbprint", certCollection[0].Thumbprint);
}
var result = client.GetAsync(uri).GetAwaiter().GetResult();
Console.WriteLine($"status: {result.StatusCode}");
Console.WriteLine($"content: {result.Content.ReadAsStringAsync().GetAwaiter().GetResult()}");
Console.ReadLine();
}
On the server side, I added a middleware to get the certificate details.
{
//var certHeader = context.Request.Headers["X-ARR-ClientCert"];
var certificate = context.Connection.ClientCertificate;
if (certificate != null)
{
try
{
//var clientCertBytes = Convert.FromBase64String(certHeader);
//var certificate = new X509Certificate2(clientCertBytes);
bool isValidCert = IsValidClientCertificate(certificate);
if (isValidCert)
{
await _next.Invoke(context);
}
else
{
_logger.LogError("Certificate is not valid");
context.Response.StatusCode = 403;
}
}
catch (Exception ex)
{
_logger.LogError(ex.Message, ex);
await context.Response.WriteAsync(ex.Message);
context.Response.StatusCode = 403;
}
}
else
{
_logger.LogError("X-ARR-ClientCert header is missing");
context.Response.StatusCode = 403;
}
}
I tried running it on the local machine and on Azure App Services.
I set the flag clientCertEnabled to true in resources.azure.com.
Web Api is SSL enabled.
But the certificate is always coming as null in both
var certHeader = context.Request.Headers["X-ARR-ClientCert"]; and
var certificate = context.Connection.ClientCertificate;.
What am I doing wrong here?
Thanks in advance.

Was reasearching some of this stuff myself and seeing your question, I wonder if you are having one of the issues described in the link Securing ASP.NET WebAPI using Client Certificates
Specifically:
4 Add MyPersonalCA.cer to Local Machine -> Truted Root Certificate Authorities. This is key, espcially while you are developing and want to try things. If you don’t do it, Request.ClientCertificate won’t be populated because the cert chain is untrusted.

Related

Trying to access Azure Keyvault from .Net Framework project is hanging

I'm currently working on a 256 bit AES encryption API project for my job. One of the aspects of these encryption APIs is they need to access our Azure Keyvault to retrieve a key (we have different keys for different projects).
For some reason the .Net Framework project hangs when trying to access the key vault after the first successful execution. It will hang on this line: var key = client.GetKeyAsync($"https://automationkeys.vault.azure.net/keys/{product}").GetAwaiter().GetResult();
I have the same encryption API made using .Net Core and I'm able to execute calls multiple times in a row without issue.
After doing some reading I have a feeling it has to do with async / await but I don't know enough about all that to see where the problem is.
Here is my full KeyVaultAccessor class:
public static class KeyVaultAccessor
{
public static string GetKey(string product)
{
var keyValue = string.Empty;
try
{
var client = GetKeyVaultClient(<my_app_id>, <keyvault_cert_thumbprint>);
var key = client.GetKeyAsync($"https://automationkeys.vault.azure.net/keys/{product}").GetAwaiter().GetResult();
keyValue = key?.KeyIdentifier.Version;
if (string.IsNullOrEmpty(keyValue))
{
Assert.Fail($"Key was null or empty for product: {product}");
}
}
catch (Exception e)
{
Assert.Fail($"Error occurred while attempting to retrieve key for product: {product}. {e.Message}");
}
return keyValue;
}
private static KeyVaultClient GetKeyVaultClient(string appId, string thumbprint)
{
var keyVault = new KeyVaultClient(async (authority, resource, scope) =>
{
var authenticationContext = new AuthenticationContext(authority, null);
X509Certificate2 certificate;
var store = new X509Store(StoreName.My, StoreLocation.LocalMachine);
try
{
store.Open(OpenFlags.ReadOnly);
var certificateCollection = store.Certificates.Find(X509FindType.FindByThumbprint, thumbprint, false);
if (certificateCollection.Count == 0)
{
throw new Exception("<certificate name> not installed in the store");
}
certificate = certificateCollection[0];
}
finally
{
store.Close();
}
var clientAssertionCertificate = new ClientAssertionCertificate(appId, certificate);
var result = await authenticationContext.AcquireTokenAsync(resource, clientAssertionCertificate);
return result.AccessToken;
});
return keyVault;
}
}
Not quite sure your root reason, but if you want to get a key in Azure keyVault by ClientCertificateCredential and local cert, try the code below which works perfectly for me:
using System;
using System.Security.Cryptography.X509Certificates;
using System.Threading.Tasks;
using Azure.Identity;
using Azure.Security.KeyVault.Keys;
namespace key_vault_console_app
{
class Program
{
static void Main(string[] args)
{
var keyVaultName = "";
var tenantID = "";
var appID = "";
var certThumbprint = "";
var kvUri = $"https://{keyVaultName}.vault.azure.net";
var certCred = new ClientCertificateCredential(tenantID, appID, GetLocalCert(certThumbprint));
var client = new KeyClient(new Uri(kvUri), certCred);
Console.Write(client.GetKey("<your key name>").Value.Key.Id);
}
public static X509Certificate2 GetLocalCert(string thumbprint)
{
var store = new X509Store(StoreName.My, StoreLocation.LocalMachine);
try
{
store.Open(OpenFlags.ReadOnly);
var certificateCollection = store.Certificates.Find(X509FindType.FindByThumbprint, thumbprint, false);
if (certificateCollection.Count == 0)
{
throw new Exception("cert not installed in the store");
}
return certificateCollection[0];
}
finally
{
store.Close();
}
}
}
}
Result:

Azure IoT Hub Unauthorized Access for Additional Devices

I have an issue attempting to register a second device through DPS to an IoT Hub using x509 certificates. My root certificate authority is present and validated on both the DPS and IoT Hub (generated through openssl). As for the client side certificate, I'm generating it once the application starts (if to doesn't already exist) in the below code. What's bothering me is every single device gets enrolled into Azure DPS correctly but only the FIRST device gets authorized and registered. Is it possibly something I'm doing during my client side certificate creation that is messing it up? Also the error is found in this line during device registration to the IoT Hub:
DeviceRegistrationResult result = await provisioningDeviceClient.RegisterAsync().ConfigureAwait(false);
Added error:
2019/12/16 09:37:38.309|ERROR| Error found attempting to start service The device failed to register # the IoT Hub : The device failes to provision correctly: AMQP transport exception | Tidel.DeviceAgent.DeviceAgent |
CLIENT SIDE CERTIFICATE GENERATION
X509Certificate2 caRootCertificate;
X509Store caStore = new X509Store(StoreName.Root, StoreLocation.LocalMachine);
caStore.Open(OpenFlags.ReadOnly | OpenFlags.OpenExistingOnly);
X509Certificate2Collection signerCollection = (X509Certificate2Collection)caStore.Certificates.Find(X509FindType.FindByIssuerName, "CERTNAME", true);
caStore.Close();
if (signerCollection.Count != 0)
{
caRootCertificate = signerCollection[0];
using (var rsa = RSA.Create())
{
rsa.KeySize = 2048;
var clientCertificateRequest = new CertificateRequest($"CN={_writableOptions.Value.RegistrationId}", rsa, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
clientCertificateRequest.CertificateExtensions.Add(new X509BasicConstraintsExtension(false, false, 0, false));
var issuerSubjectKey = caRootCertificate.Extensions["Subject Key Identifier"].RawData;
var segment = new ArraySegment<byte>(issuerSubjectKey, 2, issuerSubjectKey.Length - 2);
var authorityKeyIdentifier = new byte[segment.Count + 4];
authorityKeyIdentifier[0] = 0x30;
authorityKeyIdentifier[1] = 0x16;
authorityKeyIdentifier[2] = 0x80;
authorityKeyIdentifier[3] = 0x14;
segment.CopyTo(authorityKeyIdentifier, 4);
clientCertificateRequest.CertificateExtensions.Add(new X509Extension("2.5.29.35", authorityKeyIdentifier, false));
var sanBuilder = new SubjectAlternativeNameBuilder();
sanBuilder.AddDnsName(_writableOptions.Value.RegistrationId);
var sanExtension = sanBuilder.Build();
clientCertificateRequest.CertificateExtensions.Add(sanExtension);
clientCertificateRequest.CertificateExtensions.Add(new X509EnhancedKeyUsageExtension(new OidCollection { new Oid("1.3.6.1.5.5.7.3.2") }, false));
clientCertificateRequest.CertificateExtensions.Add(new X509SubjectKeyIdentifierExtension(clientCertificateRequest.PublicKey, false));
var notBefore = DateTimeOffset.UtcNow.AddDays(-1);
if (notBefore < caRootCertificate.NotBefore)
{
notBefore = new DateTimeOffset(caRootCertificate.NotBefore);
}
var notAfter = DateTimeOffset.UtcNow.AddDays(365);
if (notAfter > caRootCertificate.NotAfter)
{
notAfter = new DateTimeOffset(caRootCertificate.NotAfter);
}
var epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
var unixTime = Convert.ToInt64((DateTime.UtcNow - epoch).TotalSeconds);
var serial = BitConverter.GetBytes(unixTime);
using (var cert = clientCertificateRequest.Create(caRootCertificate, notBefore, notAfter, serial))
{
X509Certificate2 client = cert.CopyWithPrivateKey(rsa);
return await Task.FromResult(client);
}
}
}
else
{
throw new FileNotFoundException($"Could not find a root certificate.");
}
DEVICE ENROLLMENT TO DPS
Attestation attestation = X509Attestation.CreateFromClientCertificates(new X509Certificate2(certificate.Export(X509ContentType.Cert)));
IndividualEnrollment individualEnrollment = new IndividualEnrollment(_writableOptions.Value.RegistrationId, attestation)
{
DeviceId = _writableOptions.Value.DeviceId,
ProvisioningStatus = ProvisioningStatus.Enabled
};
individualEnrollmentResult = await _provisioningServiceClient.CreateOrUpdateIndividualEnrollmentAsync(individualEnrollment).ConfigureAwait(false);
DEVICE REGISTRATION TO IOT HUB
using (var certificatePassword = new X509Certificate2(certificate.GetRawCertData(), _writableOptions.Value.CertPass))
{
using (var security = new SecurityProviderX509Certificate(certificatePassword))
{
using (var transport = new ProvisioningTransportHandlerAmqp(TransportFallbackType.TcpOnly))
{
ProvisioningDeviceClient provisioningDeviceClient = ProvisioningDeviceClient.Create(_writableOptions.Value.AzureEndpoint, _writableOptions.Value.IdScope, security, transport);
DeviceRegistrationResult result = await provisioningDeviceClient.RegisterAsync().ConfigureAwait(false);
IAuthenticationMethod authenticationMethod = new DeviceAuthenticationWithX509Certificate(result.DeviceId, certificate);
DeviceClient deviceClient = DeviceClient.Create(result.AssignedHub, authenticationMethod, TransportType.Amqp_Tcp_Only);
return await Task.FromResult(deviceClient);
}
}
}
I figured out the issue. When the certificate was generated in the store, I was using FindByIssuerName to locate the certificate.
X509Certificate2Collection signerCollection = (X509Certificate2Collection)caStore.Certificates.Find(X509FindType.FindByIssuerName, "CERTNAME", true);
After investigating further, there were TWO certificates with the exact same name in the store. The issue: MMC snap-in was only showing one certificate. After looking around, it was suggested somewhere to run a storerepair command on the store. After running the store repair command, I could then see both certificates in MMC and was able to remove the offending certificate preventing the valid one from being detected.
Windows Version: Windows Embedded 8.1 Industry Pro

Adding an SSL Certificate programmatically to get working https

I have some code that is supposed to add an ssl certifacate to a port. It does say it has succesfully added a certificate, but when i host the website and then type in the localhost url (localhost:{port}) the page keeps loading. I don't understand why it doesn't work.
I already tried to host the website without ssl and that worked fine so that's not the problem.
public static class Certificate
{
public static void Standard(int port)
{
var certSubjectName = "HelloMyNameIsHank";
var expiresIn = TimeSpan.FromHours(1);
var cert = GenerateCert(certSubjectName, expiresIn);
Console.WriteLine("Generated certificate, {0}Thumbprint: {1}{0}", Environment.NewLine, cert.Thumbprint);
RegisterSslOnPort(port, cert.Thumbprint);
Console.WriteLine($"Registerd SSL on port: {port}");
}
private static void RegisterSslOnPort(int port, string certThumbprint)
{
var appId = Guid.NewGuid();
string arguments = $"http add sslcert ipport=0.0.0.0:{port} certhash={certThumbprint} appid={{{appId}}}";
ProcessStartInfo procStartInfo = new ProcessStartInfo("netsh", arguments);
procStartInfo.RedirectStandardOutput = true;
procStartInfo.UseShellExecute = false;
procStartInfo.CreateNoWindow = true;
var process = Process.Start(procStartInfo);
while (!process.StandardOutput.EndOfStream)
{
string line = process.StandardOutput.ReadLine();
Console.WriteLine(line);
}
process.WaitForExit();
}
public static X509Certificate2 GenerateCert(string certName, TimeSpan expiresIn)
{
var store = new X509Store(StoreName.Root, StoreLocation.LocalMachine);
store.Open(OpenFlags.ReadWrite);
var existingCert = store.Certificates.Find(X509FindType.FindBySubjectName, certName, false);
if (existingCert.Count > 0)
{
store.Close();
return existingCert[0];
}
else
{
var cert = CreateSelfSignedCertificate(certName, expiresIn);
store.Add(cert);
store.Close();
return cert;
}
}
public static X509Certificate2 CreateSelfSignedCertificate(string subjectName, TimeSpan expiresIn)
{
// 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.Add(expiresIn);
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)
System.Security.Cryptography.X509Certificates.X509KeyStorageFlags.Exportable
);
}
}
I want to be able to host my website with https. If anyone knows why this doesn't work please tell me.
I'm using httplistener for hosting. I don't want to use ASP.NET and/or IIS.
I'm not to familiar with ssl certificates so I would also really appreciate some code snippets even if it's just pseudo-code.

Not able to consume Zoopla webservice in c#

I want to consume a web service (https://realtime-listings-api.webservices.zpg.co.uk/sandbox/v1/listing/list) and as per there documentation (https://realtime-listings.webservices.zpg.co.uk/docs/latest/documentation.html)
I have to send .crt and .pem file for authentication.
I am able to load .crt file but for .pem I am getting error that Cannot find the requested object . I have tried different method to load PEM file.
I have followed following threads but still not able to load X509Certificate from .pem file.
My code is as below
var webAddr = "https://realtime-listings-api.webservices.zpg.co.uk/sandbox/v1/listing/list";
var httpWebRequest = (HttpWebRequest)WebRequest.Create(webAddr);
httpWebRequest.ContentType = "application/json; profile=http://realtime-listings.webservices.zpg.co.uk/docs/v1.1/schemas/listing/list.json";
httpWebRequest.Method = "POST";
httpWebRequest.ClientCertificates.Add(X509Certificate.CreateFromCertFile(#"E:\ProcessZooplaData\zpg_realtime_listings_14810206-20261204.crt"));
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
Now till here everything is Okay now If I try to load .pem file then I am getting error
var pem = System.IO.File.ReadAllText(#"E:\\ProcessZooplaData\\private.pem");
byte[] certBuffer = GetBytesFromPEM(pem, "RSA PRIVATE KEY");
var certificate = new X509Certificate(certBuffer);
httpWebRequest.ClientCertificates.Add(certificate);
byte[] GetBytesFromPEM(string pemString, string section)
{
var header = String.Format("-----BEGIN {0}-----", section);
var footer = String.Format("-----END {0}-----", section);
var start = pemString.IndexOf(header, StringComparison.Ordinal);
if (start < 0)
return null;
start += header.Length;
var end = pemString.IndexOf(footer, start, StringComparison.Ordinal) - start;
if (end < 0)
return null;
return Convert.FromBase64String(pemString.Substring(start, end));
}
I am getting error here that Cannot find the requested object .
Rest of code is as below
using (var streamWriter = new StreamWriter(httpWebRequest.GetRequestStream()))
{
string json = "{\"branch_reference\":\"test\"}";
streamWriter.Write(json);
streamWriter.Flush();
}
var httpResponse = (HttpWebResponse)httpWebRequest.GetResponse();
using (var streamReader = new StreamReader(httpResponse.GetResponseStream()))
{
var result = streamReader.ReadToEnd();
//return result;
}
I have tried following threads for reference
how to get private key from PEM file?
http://pages.infinit.net/ctech/20040812-0816.html
If your certificate has already been loaded into your cert store, then you can do the following:
var requestHandler = new WebRequestHandler();
var store = new X509Store("My", StoreLocation.LocalMachine);
store.Open(OpenFlags.ReadOnly);
var certificates = store.Certificates.Find(X509FindType.FindBySubjectDistinguishedName, certificateName, true);
if (certificates.Count > 0)
requestHandler.ClientCertificates.Add(certificates[0]);
else
throw new Exception(string.Format("Can't find certificate {0}", certificateName));
using (var client = new HttpClient(requestHandler))
{
do work!
}
which should add the cert from the store to the connection.
Then you'll need to open up the cert store in the Certificates (run mmc, add Certificates to the console for the local computer store), browse to your certificate, right click it and select All Tasks > Manage Private Keys and grant the user account your application will run under "read" access to the cert's private key and you won't need to open any files to do this.
Of course, if you're doing this on a shared hosting environment where you don't get access to the certs or can't install them into the cert store, that's a different problem.

No certificate found

I'm trying to authenticate a user for the Azure management API. I'm following this tutorial for that:
http://msdn.microsoft.com/en-us/library/windowsazure/ee460782.aspx
However, when I execute it, I always get a "certificate not found" error. What could cause this? My code is exactly the same as in the example:
// Values for the subscription ID and List Hosted Services operation.
//
string subscriptionId = "****************";
// The opperation to be performed. This value can be modified to reflect the operation being performed.
string operationName = "hostedservices";
// Build a URI for https://management.core.windows.net/<subscription-id>/services/<operation-type>
Uri requestUri = new Uri("https://management.core.windows.net/"
+ subscriptionId
+ "/services/"
+ operationName);
// Create the request and specify attributes of the request.
HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(requestUri);
// Define the requred headers to specify the API version and operation type.
request.Headers.Add("x-ms-version", "2010-10-28");
request.Method = "GET";
request.ContentType = "application/xml";
// The thumbprint value of the management certificate.
// You must replace the string with the thumbprint of a
// management certificate associated with your subscription.
string certThumbprint = "*************";
// Create a reference to the My certificate store.
X509Store certStore = new X509Store(StoreName.My, StoreLocation.CurrentUser);
// Try to open the store.
try
{
certStore.Open(OpenFlags.ReadOnly);
}
catch (Exception e)
{
if (e is CryptographicException)
{
Console.WriteLine("Error: The store is unreadable.");
}
else if (e is SecurityException)
{
Console.WriteLine("Error: You don't have the required permission.");
}
else if (e is ArgumentException)
{
Console.WriteLine("Error: Invalid values in the store.");
}
else
{
throw;
}
}
// Find the certificate that matches the thumbprint.
X509Certificate2Collection certCollection = certStore.Certificates.Find(X509FindType.FindByThumbprint, certThumbprint, false);
certStore.Close();
// Check to see if our certificate was added to the collection. If no, throw an error, if yes, create a certificate using it.
if (0 == certCollection.Count)
{
throw new Exception("Error: No certificate found containing thumbprint " + certThumbprint);
}
// Create an X509Certificate2 object using our matching certificate.
X509Certificate2 certificate = certCollection[0];
// Attach the certificate to the request.
request.ClientCertificates.Add(certificate);
try
{
// Make the call using the web request.
HttpWebResponse response = (HttpWebResponse)request.GetResponse();
// Display the web response status code.
Console.WriteLine("Response status code: " + response.StatusCode);
// Display the request ID returned by Windows Azure.
if (null != response.Headers)
{
Console.WriteLine("x-ms-request-id: "
+ response.Headers["x-ms-request-id"]);
Console.ReadKey();
}
// Parse the web response.
Stream responseStream = response.GetResponseStream();
StreamReader reader = new StreamReader(responseStream);
// Display the raw response.
Console.WriteLine("Response output:");
Console.WriteLine(reader.ReadToEnd());
Console.ReadKey();
// Close the resources no longer needed.
response.Close();
responseStream.Close();
reader.Close();
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
}

Categories

Resources