The location of the certificates
Inherit auth from parent
The test in postman
I want to use Restsharp to send this GET request -
This is what I want to send -
Request - GET,
URL - https://18.0.1.230:8080/api/report/test,
Header - Key = Content-Type , Value = application/x-www-form-urlencoded
On Postman , I am not sending anything on Authorisation type (its set on "Inherit auth from parent") check 'inherit auth from parent' image
I have set Client certificates in Postman, so I have set the location of them, which postman uses when sending this request. Check 'Location of certificate' image
I want to know how do I add these certificates to a GET request using restSharp c# code, so that I pass the authentication and can get a 200 response?
What you need to do is first import the certificates:
public static async Task<X509Certificate2> LoadPemCertificate(string certificatePath, string privateKeyPath)
{
using var publicKey = new X509Certificate2(certificatePath);
var privateKeyText = await File.ReadAllTextAsync(privateKeyPath);
var privateKeyBlocks = privateKeyText.Split("-", StringSplitOptions.RemoveEmptyEntries);
var privateKeyBytes = Convert.FromBase64String(privateKeyBlocks[1]);
using var rsa = RSA.Create();
if (privateKeyBlocks[0] == "BEGIN PRIVATE KEY")
{
rsa.ImportPkcs8PrivateKey(privateKeyBytes, out _);
}
else if (privateKeyBlocks[0] == "BEGIN RSA PRIVATE KEY")
{
rsa.ImportRSAPrivateKey(privateKeyBytes, out _);
}
var keyPair = publicKey.CopyWithPrivateKey(rsa);
return new X509Certificate2(keyPair.Export(X509ContentType.Pfx));
}
Once you're you have loaded the certificate in memory, all you have to do is attach certificate to restsharp client:
var client = new RestClient("https://18.0.1.230:8080/api/report/test");
client.ClientCertificates.Add(new X509Certificate(certificate));
...
Related
Update
It turned out, that we have a problem with the SslStream (on which the HttpClient is based on). In the current version (7.xx) the certificate chain is not submitted to the server, when sent via the client. This is a known issue and discussed here and here.
I will leave this post online, since the code below might be helpful to others (it does not cause problems if you want to use the client certificate only in your requests).
I have spent a lot of time trying to find our what's wrong with the Client Certificate authentication using ECDsa based certificates with the native HttpClient of .Net Core (version 7.0.100 but also tried v.6xxx) but never got this thing running. (Btw. I used the same approach for RSA based Client Certificates without any problems).
Due to security reasons, I MUST use ECDsa client certificate + chain.
I can not understand or find information why this is not working / supported and the results are confusing to me.
When loading the certificate and the key and using them to sign and verify some data, all tests pass (see code).
In the end, the required Client Certificates are not sent to the server, resulting in an SSL exception (tested the same certificates with a Python script to verify that they are correct, there I had zero problems).
I can not imagine (at least I hope not) that these kind of Client Certificates are not supported. I would greatly appreciate any help or hints for alternative workarounds. It would be quite terrible to switch to some different language at this point :/
Reference Code
Contains a test certificate and key to play around with. The chain is missing in this example and can simply be attached to the certificate string
Part of the tests are taken from: Use X509Certificate2 to sign and validate ECDSA-SHA256 signatures
[Test]
// Just some test to better understand whether the certificate and key is working and belong together
public void TestEcdsaFunctionality()
{
var ecdsaCertificate = #"-----BEGIN CERTIFICATE-----
MIIBgzCCASoCCQD+iUUbrH+BOzAKBggqhkjOPQQDAjBKMQswCQYDVQQGEwJQRDEL
MAkGA1UECAwCQlcxEDAOBgNVBAcMB1BhbmRvcmExDzANBgNVBAoMBkFueU9yZzEL
MAkGA1UEAwwCNDIwHhcNMjIxMTIxMTE0MjExWhcNMjMxMTIxMTE0MjExWjBKMQsw
CQYDVQQGEwJQRDELMAkGA1UECAwCQlcxEDAOBgNVBAcMB1BhbmRvcmExDzANBgNV
BAoMBkFueU9yZzELMAkGA1UEAwwCNDIwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNC
AAT6vBU2iIcESep8UeQhfNFgfTArFYvtb2Pmlbk1+R9gdNaWEg1UK7dlt3/mH/X3
Mrg80JaTY3OPM92MY9e9gs7ZMAoGCCqGSM49BAMCA0cAMEQCIA3p2mMOYqGEzReY
br7nYLsLdF0+dV6iZSZaG1iMHwblAiA5UaJlVr5CsCkG+j1ZJEICSOnVMyx4DjA5
oZuoMYa42w==
-----END CERTIFICATE-----";
var ecdsaPrivateKey = #"MDECAQEEIM6BExC2G7P1KpViQmZ/Z65nukv8yQmvw6PqGGQcKn9boAoGCCqGSM49
AwEH";
var cert = X509Certificate2.CreateFromPem(ecdsaCertificate.ToCharArray());
var key = ECDsa.Create("ECDsa");
var keybytes = Convert.FromBase64String(ecdsaPrivateKey);
key.ImportECPrivateKey(keybytes, out _);
var helloBytes = Encoding.UTF8.GetBytes("Hello World");
// Sign data with the ECDsa key
var signed = key.SignData(helloBytes, 0, helloBytes.Count(), HashAlgorithmName.SHA256);
// Verify the data signature with the certificates public key
var verified = cert.GetECDsaPublicKey().VerifyData(helloBytes, signed, HashAlgorithmName.SHA256);
// Assume that everything went well and the data signature is valid
Assert.That(verified, Is.EqualTo(true));
// Additional tests with the X509Certificate2 object type
X509Certificate2 keyCert = ECDsaCertificateExtensions.CopyWithPrivateKey(cert, key);
// Sing using the certificate that contains the private key
using (ECDsa ecdsa = keyCert.GetECDsaPrivateKey())
{
if (ecdsa == null)
throw new ArgumentException("Cert must have an ECDSA private key", nameof(cert));
signed = ecdsa.SignData(helloBytes, HashAlgorithmName.SHA256);
}
// Verify signed data using the certificate that contains the private key
using (ECDsa ecdsa = keyCert.GetECDsaPublicKey())
{
if (ecdsa == null)
throw new ArgumentException("Cert must be an ECDSA cert", nameof(cert));
Assert.That(ecdsa.VerifyData(helloBytes, signed, HashAlgorithmName.SHA256), Is.EqualTo(true));
}
WorkshopRegistration(keyCert);
}
// This would be what I really want to use the client certificate for
private void WorkshopRegistration(X509Certificate2 clientCert)
{
var payload = "{somepayload}";
var handler = new HttpClientHandler();
handler.ClientCertificateOptions = ClientCertificateOption.Manual;
handler.SslProtocols = SslProtocols.Tls12;
handler.ClientCertificates.Add(clientCert);
handler.ServerCertificateCustomValidationCallback =
(httpRequestMessage, cert, cetChain, policyErrors) =>
{
return true;
};
var content = new StringContent(payload, System.Text.Encoding.UTF8, "application/json");
var client = new HttpClient(handler);
client.DefaultRequestHeaders.Add("Accept", "application/json");
var result = client.PutAsync("https://someHostname.com/registration",
content).GetAwaiter().GetResult();
if (result.StatusCode != HttpStatusCode.OK)
throw new SuccessException("Registration failed with conent: " + result.Content.ToString());
}
I'm trying to implement a C# program to connect to Sharepoint API through modern authentication (Client ID\ Client Secret).
I've registered an APP with Sharepoint overall permissions on Azure Active Directory, in order to generate Client Id and Client Secret.
Next steps should be retrieval of the Access Token from the Microsoft login page, and then construction of all following requests using the bearing token I've generated.
Retrieval of the Access Token just works fine. The problem is when I try to include the token in the authorization header on the following calls.
I always get 401 Unhautorized when building my requests from code. Debugging the response content, what I get is "x-ms-diagnostics: 3000006;reason="Token contains invalid signature"; category"invalid_client".
Instead if I try to replicate the call in Postman I get the following error "{"error_description":"Unsupported security token."}".
I provide my code below. Does anybody knows what is going on?
var b2cAuthUri = "https://login.microsoftonline.com/" + tenantId + "/oauth2/v2.0/token";
var client = new HttpClient();
var dict = new Dictionary<string, string>();
dict.Add("Content-Type", "application/x-www-form-urlencoded");
dict.Add("grant_type", "client_credentials");
dict.Add("client_id", clientId);
dict.Add("client_secret", clientSecret);
dict.Add("scope", scope);
// Execute post method
using (var methodResp = client.PostAsync(b2cAuthUri, new FormUrlEncodedContent(dict)))
{
var callResult = methodResp.Result.Content.ReadAsStringAsync().Result;
if (!string.IsNullOrEmpty(callResult))
{
//I have my Access Token here :)
using (MemoryStream DeSerializememoryStream = new MemoryStream())
{
//initialize DataContractJsonSerializer object and pass custom token class type to it
DataContractJsonSerializer serializer = new DataContractJsonSerializer(typeof(AccessToken));
//user stream writer to write JSON string data to memory stream
StreamWriter writer = new StreamWriter(DeSerializememoryStream);
writer.Write(callResult);
writer.Flush();
DeSerializememoryStream.Position = 0;
//get the Desrialized data in object of type Student
AccessToken SerializedObject = (AccessToken)serializer.ReadObject(DeSerializememoryStream);
var tokenBytes = System.Text.Encoding.UTF8.GetBytes(SerializedObject.access_token);
//64bit serialized token
var tokenBase64 = System.Convert.ToBase64String(tokenBytes);
//Here I try to make a call with the access token as header
var testURI = "https://myorg.sharepoint.com/sites/crmkb/_api/web/lists";
HttpWebRequest testReq = (HttpWebRequest)HttpWebRequest.Create(testURI);
testReq.Headers.Add(HttpRequestHeader.Authorization, "Bearer " + tokenBase64);
testReq.Method = "GET";
//This fails on 401 code
HttpWebResponse response = (HttpWebResponse)testReq.GetResponse();
}
}
}
SharePoint Online has blocked the Azure AD App Client Secret, so if you want to use Azure AD App to authentication with SharePoint Rest API, it's necessary to use Certificate option:
Calling SharePoint Online APIs using Azure AD App-Only permissions and certificate auth
Another option is to use the SharePoint hosted App Id/ Secret registered in "/_layouts/15/appregnew.aspx", this way supported the Client Secret, please check the demo test in Postman:
Accessing SharePoint Data using Postman (SharePoint REST API)
I'm actually trying to expose some methods of an ASP.NET MVC specific controller, in order to secure sensitive calls.
The entire website doesn't have to be protected by a specific SSL certificate, but some requests are.
Here is my code (as simple as it is) to get "Data", as you can see, I first check the SSL certificate, then the process continues if the SSL Certificate is correct :
public string GetData()
{
try
{
var certificate = Request.ClientCertificate;
if (certificate == null || string.IsNullOrEmpty(certificate.Subject))
{
// certificate may not be here
throw new Exception("ERR_00_NO_SSL_CERTIFICATE");
}
if (!certificate.IsValid || !IsMyCertificateOK(certificate))
{
// certificate is not valid
throw new Exception("ERR_01_WRONG_SSL_CERTIFICATE");
}
// Actions here ...
}
catch (Exception)
{
Response.StatusCode = 400;
Response.StatusDescription = "Bad Request";
}
}
Here is my IIS configuration :
SSL Certificate is set to "Accept", thus, I hope I could get the client certificate in the Request.ClientCertificate property, but it's never the case, I never get the certificate set in my client.
Here is my client code (copied from generated Postman C# code) :
string PFX_PATH = #"C:\Test\test.pfx"; // set here path of the PFX file
string PFX_PASSWORD = "password"; // set here password of the PFX file
var client = new RestClient("https://mywebsite.com/GetData?input=test");
client.Timeout = -1;
client.ClientCertificates = new System.Security.Cryptography.X509Certificates.X509CertificateCollection()
{
new System.Security.Cryptography.X509Certificates.X509Certificate(PFX_PATH,
PFX_PASSWORD,
System.Security.Cryptography.X509Certificates.X509KeyStorageFlags.Exportable)
};
var request = new RestRequest(Method.GET);
IRestResponse response = client.Execute(request);
Console.WriteLine(response.Content);
The PFX file has a private key, and is accessible from client side.
Am I missing something regarding the IIS configuration, or should I update my web.config somehow ?
My question relates to the following posts:
HttpClient does not send client certificate on Windows using .NET Core
WebApi HttpClient not sending client certificate
My env:
Mac osx ( 10.15.6 )
Visual studio code for Mac ( 8.3.2 )
Goal:
I want to use self-signed certificates ( as an experiment ), to test TLS with c# and a nginx server. I can use this curl command to confirm that the crt and key allow me to access a resource:
curl --cert client.crt --key client.key -k https://localhost:443
I read the p12 keystore using this code
public X509Certificate2 readCertificates(String path, String password)
{
return new X509Certificate2(File.ReadAllBytes(path), password);
}
I use this code to make a Https request. The loaded certificate has a subject, identifier, and private key that I expect. This confirms the above step.
public async System.Threading.Tasks.Task validateRequest(X509Certificate2 certificate)
{
try
{
WebRequestHandler handler = new WebRequestHandler();
handler.ClientCertificates.Add(certificate);
handler.SslProtocols = SslProtocols.Tls12 | SslProtocols.Tls11 | SslProtocols.Tls;
ServicePointManager.ServerCertificateValidationCallback = (a, b, c, d) => { return true; };
handler.ClientCertificateOptions = ClientCertificateOption.Manual;
HttpClient client = new HttpClient(handler);
HttpRequestMessage httpContent = new HttpRequestMessage(HttpMethod.Get, new Uri("https://localhost:443"));
HttpResponseMessage response = await client.SendAsync(httpContent);
string resultContent = response.Content.ReadAsStringAsync().Result;
if (response.IsSuccessStatusCode)
{
Console.WriteLine("## we authenticated...");
}
else {
Console.WriteLine("### we have an issue");
}
response.Dispose();
client.Dispose();
httpContent.Dispose();
}
catch (Exception e) {
Console.WriteLine("##### error:: " + e);
}
}
After executing this code, my ngix server states:
client sent no required SSL certificate while reading client request header
The c# side evalutes $resultContent$ to:
400 No required SSL certificate was sent
If I replace the below lines, I get a different error:
// take this line
ServicePointManager.ServerCertificateValidationCallback = (a, b, c, d) => { return true; };
// replace it with the below
handler.ServerCertificateValidationCallback = (a, b, c, d) => { return true; };
I can observe an error on the ngix server with:
peer closed connection in SSL handshake while SSL handshaking
I can observe this error on the c# side:
Mono.Security.Interface.TlsException: CertificateUnknown
In reading the aforementioned stack posts, I was under the impression that I could read and ignore self-signed certificates by modifying ServerCertificateValidationCallback. This experiment is leading me to believe otherwise. Am I missing a key step in using self-signed certificates in making a https request?
I am trying to create a console app that can call the Azure Resource Rate API using certificate authentication. For this, I used the following branch GitHub link.
I am getting a 403 error. I've added an Web App to my Azure AD. In the manifest, I've copied the key credentials from the certificate I've signed using the following PowerShell commands;
$cert=New-SelfSignedCertificate -Subject "CN=RateCardCert"
-CertStoreLocation "Cert:\CurrentUser\My" -KeyExportPolicy Exportable -KeySpec Signature
$bin = $cert.RawData $base64Value = [System.Convert]::ToBase64String($bin)
$bin = $cert.GetCertHash()
$base64Thumbprint = [System.Convert]::ToBase64String($bin)
$keyid = [System.Guid]::NewGuid().ToString()
$jsonObj = # customKeyIdentifier=$base64Thumbprint;keyId=$keyid;type="AsymmetricX509Cert";usage="Verify";value=$base64Value}
$keyCredentials=ConvertTo-Json #($jsonObj) | Out-File "keyCredentials.txt"
In de console app, I use the following function to get the token;
public static string GetOAuthTokenFromAAD_ByCertificate(string TenanatID, string ClientID, string CertificateName)
{
//Creating the Authentication Context
var authContext = new AuthenticationContext(string.Format("https://login.windows.net/{0}", TenanatID));
//Console.WriteLine("new authContext made");
//Creating the certificate object. This will be used to authenticate
X509Certificate2 cert = null;
//Console.WriteLine("empty 'cert' made, null");
//The Certificate should be already installed in personal store of the current user under
//the context of which the application is running.
X509Store store = new X509Store(StoreName.My, StoreLocation.CurrentUser);
try
{
//Trying to open and fetch the certificate
store.Open(OpenFlags.ReadOnly);
var certCollection = store.Certificates;
var certs = certCollection.Find(X509FindType.FindBySubjectName, CertificateName, false);
//Checking if certificate found
if (certs == null || certs.Count <= 0)
{
//Throwing error if certificate not found
throw new Exception("Certificate " + CertificateName + " not found.");
}
cert = certs[0];
}
finally
{
//Closing the certificate store
store.Close();
}
//Creating Client Assertion Certificate object
var certCred = new ClientAssertionCertificate(ClientID, cert);
//Fetching the actual token for authentication of every request from Azure using the certificate
var token = authContext.AcquireToken("https://management.core.windows.net/", certCred);
//Optional steps if you need more than just a token from Azure AD
//var creds = new TokenCloudCredentials(subscriptionId, token.AccessToken);
//var client = new ResourceManagementClient(creds);
//Returning the token
return token.AccessToken;
}
This is the part of the code that makes the URL and puts in the request (the xxxx part is replaced by the ClientID of the webapp that I've registered in Azure AD);
//Get the AAD User token to get authorized to make the call to the Usage API
string token = GetOAuthTokenFromAAD_ByCertificate("<MyTenantName.onmicrosoft.com", "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", "RateCardCert");
// Build up the HttpWebRequest
string requestURL = String.Format("{0}/{1}/{2}/{3}",
ConfigurationManager.AppSettings["ARMBillingServiceURL"],
"subscriptions",
ConfigurationManager.AppSettings["SubscriptionID"],
"providers/Microsoft.Commerce/RateCard?api-version=2015-06-01-preview&$filter=OfferDurableId eq 'MS-AZR-0044P' and Currency eq 'EUR' and Locale eq 'nl-NL' and RegionInfo eq 'NL'");
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(requestURL);
// Add the OAuth Authorization header, and Content Type header
request.Headers.Add(HttpRequestHeader.Authorization, "Bearer " + token);
request.ContentType = "application/json";
// Call the RateCard API, dump the output to the console window
try
{
// Call the REST endpoint
Console.WriteLine("Calling RateCard service...");
HttpWebResponse response = (HttpWebResponse)request.GetResponse();
Console.WriteLine(String.Format("RateCard service response status: {0}", response.StatusDescription));
Stream receiveStream = response.GetResponseStream();
// Pipes the stream to a higher level stream reader with the required encoding format.
StreamReader readStream = new StreamReader(receiveStream, Encoding.UTF8);
var rateCardResponse = readStream.ReadToEnd();
Console.WriteLine("RateCard stream received. Press ENTER to continue with raw output.");
Console.ReadLine();
Console.WriteLine(rateCardResponse);
Console.WriteLine("Raw output complete. Press ENTER to continue with JSON output.");
Console.ReadLine();
// Convert the Stream to a strongly typed RateCardPayload object.
// You can also walk through this object to manipulate the individuals member objects.
RateCardPayload payload = JsonConvert.DeserializeObject<RateCardPayload>(rateCardResponse);
Console.WriteLine(rateCardResponse.ToString());
response.Close();
readStream.Close();
Console.WriteLine("JSON output complete. Press ENTER to close.");
Console.ReadLine();
}
catch(Exception e)
{
Console.WriteLine(String.Format("{0} \n\n{1}", e.Message, e.InnerException != null ? e.InnerException.Message : ""));
Console.ReadLine();
}
I just don't know what I have to do anymore, what am I missing here??
Full return of the console is:
Calling RateCard Service...
The remote server returned an error: (403) Forbidden.
After a chat in the comments the problem was found:
You are calling the Azure Resource Management API, but you gave permissions only on the Azure Service Management API. You need to add your app's service principal to a role in your subscription. Find your subscription, then the Access Control (IAM) blade, and then add your app to a role there. You should be able to find it with its name.
You can also add the service principal to a role on a resource group or even specific resources if you want to limit its capabilities.