Invalid remote certificate in HTTPClient (.net core), but works from Postman - powershell

I just created an empty .NET Core Console application, from which I want to reach a 3rd party API via HttpClient. The endpoint requires an SSL certificate, as well as an APIKey and Username in the request's headers. I've setup the call in Postman (and Visual Studio Code's REST Client extension) for testing purposes, and I'm getting a 200 back, with the expected payload. However, when using HttpClient (or RestSharp, for that matter), I'm getting the following exception:
The SSL connection could not be established, see inner exception.
The remote certificate is invalid according to the validation procedure.
Here's my code (mocked in the example the url, apikey, username, certificate base64 string and the proxy my company uses):
using (var request = new HttpRequestMessage(HttpMethod.Get, "validUrl"))
{
var certi = "..."; // base64 string
var bytes = Convert.FromBase64String(certi);
var pfxCert = new X509Certificate2Collection();
pfxCert.Import(bytes, null, X509KeyStorageFlags.MachineKeySet);
request.Headers.Add("apikey", "validApiKey");
request.Headers.Add("username", "myUser");
var handler = new HttpClientHandler();
handler.ClientCertificates.AddRange(pfxCert);
handler.Proxy = new WebProxy("myValidCorporateProxy", false);
using (var httpClient = new HttpClient(handler))
{
using (var response = httpClient.SendAsync(request).Result)
{
var content = response.Content.ReadAsStringAsync().Result;
}
}
}
I also tried to play around with ServicePointManager, with no luck:
ServicePointManager.ServerCertificateValidationCallback = delegate { return true; };
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls11; // tested with Tls, Tls11, Tls12
ServicePointManager.Expect100Continue = true;
Maybe I'm failing to convert the .pfx into a base64 encoded string? I've used to following PowerShell commands:
$fileContentBytes = get-content 'C:\Users\myUser\Desktop\certificate.pfx' -Encoding Byte
[System.Convert]::ToBase64String($fileContentBytes) | Out-File ‘C:\Users\myUser\Desktop\certificate-string.txt’
UPDATE:
Using the answer from this question: .net core API Post exception gives NativeErrorCode 12175
It works:
handler.ServerCertificateCustomValidationCallback = (message, cert, chain, errors) => { return true; };
But I feel like this isn't too safe of a workaround (bypassing the validation), isn't it?
UPDATE2
When hitting ServerCertificateCustomValidationCallback, I can apparently see a more useful error: System.Net.Security.SslPolicyErrors.RemoteCertificateNameMismatch
So, I'm assuming something is either wrong with my certificate, or the server's (but Postman somehow ignores this)? Maybe I should take it up with the 3rd party.

Related

Dynamics 365 Plugin call External REST API - SSL/TSL could not create channel issue

I have a task to call the external REST API to get data from third party application.
In that, i have created a C# Console application for tried this and it is working fine and i can get the data from thirty party application via REST API.
The same code used to tried in Dynamics Custom workflow\Plugin, i have got a error below. Please give your valuable suggestion on this.
"System.Net.WebException: The request was aborted: Could not create SSL/TLS secure channel."
Note: The below options are tried but no luck.
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls12;
ServicePointManager.ServerCertificateValidationCallback += new RemoteCertificateValidationCallback(AlwaysGoodCertificate);
ServicePointManager.Expect100Continue = true;
Thanks,
Vasanth
It is not clear how you are calling external API from D365 Plugin.
// Call external API
static async Task<bool> CallExternalAPI(Guid beziehungId)
{
bool status = false;
HttpClient apiClient = new HttpClient();
apiClient.DefaultRequestHeaders.Accept.Clear();
apiClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
string url = #"https://reqres.in/api/users?page=2";
HttpResponseMessage response = await apiClient.GetAsync(url);
if (response.IsSuccessStatusCode)
{
var result = response.Content.ReadAsStringAsync().GetAwaiter().GetResult();
}
return status;
}
If your problem with SSL/TLS secure channel you can use WebClient.
// Could not create SSL/TLS secure channel.
// Use (SecurityProtocolType)3072
using (WebClient client = new WebClient())
{
client.Headers.Add(HttpRequestHeader.UserAgent, "AvoidError");
ServicePointManager.Expect100Continue = true;
//ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
ServicePointManager.SecurityProtocol = (SecurityProtocolType)3072;
htmlCode = client.DownloadString("MY LINK");
//passing the URL again to function from which to extract the content and compare from above content.
strNewCompanyCode = client.DownloadString("MY LINK");
}

coinbase api always 401 unauthorized

I have waited faithfully for well over 48 hours since generating api keys as suggested and no matter what I do, every coinbase api call gives 401 Unauthorized
I have checked basic non-authentication required calls like /time and /exchange_rates and they work fine
I am using coinbase and not coinbase pro so this is a simple api key and header based call
Have triple checked the hashing and decoding requirements, and nothing is apparent as an issue
Rather than asking to resolve coding problems from the standard examples, the ask is either
Is anyone aware of general authorization issues with the coinbase service right now ? (nothing published by coinbase)
Is there anything outside of the general code samples and documentation that needs to be done to get this damn thing authorizing ?
Any and all help appreciated.
References used :
https://developers.coinbase.com/docs/wallet/api-key-authentication#
Have added the basic code used for completeness here :
// Remove insecure protocols (SSL3, TLS 1.0, TLS 1.1)
ServicePointManager.SecurityProtocol &= ~SecurityProtocolType.Ssl3;
ServicePointManager.SecurityProtocol &= ~SecurityProtocolType.Tls;
ServicePointManager.SecurityProtocol &= ~SecurityProtocolType.Tls11;
// Add TLS 1.2
ServicePointManager.SecurityProtocol |= SecurityProtocolType.Tls12;
HttpClient client = new HttpClient();
string sTimeStamp = Convert.ToInt64(DateTime.Now.Ticks).ToString("F0", CultureInfo.InvariantCulture);
client.BaseAddress = new Uri(sUrl + sOperation);
var convertedString = Convert.FromBase64String(sAPI_EXCHANGE_SECRET);
var prehash = sTimeStamp + "GET" + sOperation + ""; // no content for now
string sSignature = HashString(prehash, convertedString);
// bunch of headers for coinbase
// Add an Accept header for JSON format.
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
client.DefaultRequestHeaders.Add("User-Agent", "CryptoAppClient"); // arbitrary user agent name
client.DefaultRequestHeaders.Add("CB-ACCESS-KEY", sAPI_EXCHANGE_KEY);
client.DefaultRequestHeaders.Add("CB-VERSION", sAPI_EXCHANGE_VERSION);
client.DefaultRequestHeaders.Add("CB-ACCESS-SIGN", sSignature);
client.DefaultRequestHeaders.Add("CB-ACCESS-TIMESTAMP", sTimeStamp);
// set up the actual call
HttpResponseMessage response = client.GetAsync("").Result;
.....
//----------------------------------------------------------------------------
private string HashString(string str, byte[] secret)
{
var bytes = System.Text.Encoding.UTF8.GetBytes(str);
using (var hmaccsha = new HMACSHA256(secret))
{
return Convert.ToBase64String(hmaccsha.ComputeHash(bytes));
}
}
Finally go it sorted, combination of timestamp value wrong and the right .net functions to encrypt the message

How to pass a file through multiple api in .net

I'm trying to download a file from Sharepoint using a REST API. Because my app is written in .Net Core, and the CSOM library doesn't support it, I've made a "sharepoint proxy" in .Net Framework, which is a single app hosted on Azure.
Now I have a problem, while trying to download a file. I send a request from Postman to my app in .Net Core, which send another request to the sharepoint proxy, which (at last) send a GET request to Sharepoint REST API. In result, I become in Sharepoint proxy a Stream from sharepoint REST API, which I try to forward back to my app. I have no idea, which format should I use to send the file. I tried WebStream, FileStream and byte[], but in each case I got an unreadable file.
Download method in .Net Core App
public async Task<Stream> DownloadFile(SharePointFileUrl spInfo)
{
var restUrl = $"{siteUrl}/downloadFile";
using (var httpClient = new HttpClient())
{
var content = new StringContent(JsonConvert.SerializeObject(spInfo), Encoding.UTF8, "application/json");
var webResponse = await httpClient.PostAsync(restUrl, content);
return await webResponse.Content.ReadAsStreamAsync();
}
}
Endpoint in Sharepoint proxy
public byte[] DownloadFile([FromBody] SharePointFileUrl fileInfo)
{
return _spService.DownloadFile(fileInfo.FileUrl);
}
Download method in Sharepoint proxy
public byte[] DownloadFile(string url)
{
var restUrl = $"{_siteUrl}/_api/web/GetFileByServerRelativeUrl('/{url}')/$value";
var request = CreateBaseRequest("GET", restUrl);
request.Headers.Add("X-RequestDigest", _formDigest);
WebResponse fileResponse = request.GetResponse();
var input = fileResponse.GetResponseStream();
using (MemoryStream ms = new MemoryStream())
{
input.CopyTo(ms);
return ms.ToArray();
}
}
Thank you in advance for any help. Of course, I've googled my problem, but without result.
In your Core app try this.
HttpWebRequest req = (HttpWebRequest)WebRequest.Create(new Uri("<file url string>"));
return File(req.GetResponse().GetResponseStream(), "<Content type>", "<download file name>");

How to call SSRS Rest-Api V1.0 with custom security implemented (NOT SOAP)

I have implemented the custom security on my reporting services 2016 and it displays the login page once the URL for reporting services is typed on browser URL bar (either reports or reportserver)
I am using the following code to pass the Credentials
when i use the code WITHOUT my security extension it works and looks like this
ICredentials _executionCredentials;
CredentialCache myCache = new CredentialCache();
Uri reportServerUri = new Uri(ReportServerUrl);
myCache.Add(new Uri(reportServerUri.GetLeftPart(UriPartial.Authority)),
"NTLM", new NetworkCredential(MyUserName, MyUserPassword));
_executionCredentials = myCache;
when i use the code WITH the security extension it doesnt work and looks like this
ICredentials _executionCredentials;
CredentialCache myCache = new CredentialCache();
Uri reportServerUri = new Uri(ReportServerUrl);
myCache.Add(new Uri(reportServerUri.GetLeftPart(UriPartial.Authority)),
"Basic", new NetworkCredential(MyUserName, MyUserPassword));
_executionCredentials = myCache;
and i get an Exception saying "The response to this POST request did not contain a 'location' header. That is not supported by this client." when i actually use this credentials
Is "basic" the wrong option ?
Have anyone done this ?
Update 1
Well it turns out that my SSRS is expecting an Authorisation cookie
which i am unable to pass (according to fiddler, there is no cookie)
HttpWebRequest request;
request = (HttpWebRequest)HttpWebRequest.Create("http://mylocalcomputerwithRS/Reports_SQL2016/api/v1.0");
CookieContainer cookieJar = new CookieContainer();
request.CookieContainer = cookieJar;
Cookie authCookie = new Cookie("sqlAuthCookie", "username:password");
authCookie.Domain = ".mydomain.mylocalcomputerwithRS";
if (authCookie != null)
request.CookieContainer.Add(authCookie);
request.Timeout = -1;
HttpWebResponse myHttpWebResponse = (HttpWebResponse)request.GetResponse();
That's how I got it (SSRS 2017; api v2.0). I took the value for the "body" from Fiddler:
var handler = new HttpClientHandler();
var httpClient = new HttpClient(handler);
Assert.AreEqual(0, handler.CookieContainer.Count);
// Create a login form
var body = new Dictionary<string, string>()
{
{"__VIEWSTATE", "9cZYKBmLKR3EbLhJvaf1JI7LZ4cc0244Hpcpzt/2MsDy+ccwNaw9hswvzwepb4InPxvrgR0FJ/TpZWbLZGNEIuD/dmmqy0qXNm5/6VMn9eV+SBbdAhSupsEhmbuTTrg7sjtRig==" },
{"__VIEWSTATEGENERATOR", "480DEEB3"},
{ "__EVENTVALIDATION", "IS0IRlkvSTMCa7SfuB/lrh9f5TpFSB2wpqBZGzpoT/aKGsI5zSjooNO9QvxIh+QIvcbPFDOqTD7R0VDOH8CWkX4T4Fs29e6IL92qPik3euu5QpidxJB14t/WSqBywIMEWXy6lfVTsTWAkkMJRX8DX7OwIhSWZAEbWZUyJRSpXZK5k74jl4x85OZJ19hyfE9qwatskQ=="},
{"txtUserName", "User"},
{"txtPassword", "1"},
{"btnLogin","Войти"}
};
var content = new FormUrlEncodedContent(body);
// POST to login form
var response = await httpClient.PostAsync("http://127.0.0.1:777/ReportServer/Logon.aspx", content);
// Check the cookies created by server
Assert.AreEqual(HttpStatusCode.OK, response.StatusCode);
var cookies = handler.CookieContainer.GetCookies(new Uri("http://127.0.0.1:777/ReportServer"));
Assert.AreEqual("sqlAuthCookie", cookies[0].Name);
// Make new request to secured resource
var myresponse = await httpClient.GetAsync("http://127.0.0.1:777/Reports/api/v2.0/Folders");
var stringContent = await myresponse.Content.ReadAsStringAsync();
Console.Write(stringContent);
As an alternative you can customize SSRS Custom Security Sample quite a bit.
I forked Microsoft's Custom Security Sample to do just what you are describing (needed the functionality at a client long ago and reimplemented as a shareable project on GitHub).
https://github.com/sonrai-LLC/ExtRSAuth
I created a YouTube walkthrough as well to show how one can extend and debug SSRS security with this ExtRSAuth SSRS security assembly https://www.youtube.com/watch?v=tnsWChwW7lA
TL; DR; just bypass the Microsoft example auth check in Login.aspx.cs and put your auth in Page_Load() or Page_Init() event of Login.aspx.cs- wherever you want to perform some custom logging check- and then immediately redirect auth'd user to their requested URI.

Must/can I install MS ASP.NET Web API Client Libraries in order to post data to my Web API server?

Do I need to install ASP.NET Web API Client Libraries (as this article indicates) in order to post data to a Web API server? If so, can I do so in Visual Studio 2008 from a Windows CE project?
The reasons I wonder are:
0) The client is a Windows CE project, for which I'm using Visual Studio 2008, and I don't know if ASP.NET Web API Client Libraries are available for that version; I know I don't have the NuGet Package Manager in that environment.
1) I am successfully querying data from my RESTful Web API methods without installing ASP.NET Web API Client Libraries, using code like this:
while (true)
{
deptList.departments.Clear();
string uri = String.Format("http://platypi:28642/api/Duckbills/{0}/{1}", lastIdFetched, RECORDS_TO_FETCH);
var webRequest = (HttpWebRequest) WebRequest.Create(uri);
webRequest.Method = "GET";
using (var webResponse = (HttpWebResponse)webRequest.GetResponse())
{
if (webResponse.StatusCode == HttpStatusCode.OK)
{
var reader = new StreamReader(webResponse.GetResponseStream());
string jsonizedDuckbills = reader.ReadToEnd();
List<Duckbill> duckbills = JsonConvert.DeserializeObject<List<Duckbill>>(jsonizedDuckbills);
if (duckbills.Count <= 0) break;
foreach (Duckbill duckbill in duckbills)
{
duckbillList.duckbills.Add(duckbill);
lastIdFetched = duckbill.Id;
}
} // if ((webResponse.StatusCode == HttpStatusCode.OK)
} // using HttpWebResponse
int recordsAdded = LocalDBUtils.BulkInsertDuckbills(duckbillList.duckbills);
totalRecordsAdded += recordsAdded;
} // while (true);
I'm stuck on posting, though, and the cleanest example I've seen so far for doing so is at that link already shown above.
I got an answer to my question on how to post here, but that hasn't made me smart enough yet to actually accomplish it. It's a step in the right direction, perhaps, although I reckon, based on how my client query code looks, that the client posting code would be of similar "style" (like the previously referenced article here, and unlike the likewise previously referenced answer here).
UPDATE
If I'm already providing the data in the uri string itself, as I am, like this:
string uri = String.Format("http://shannon2:28642/api/Departments/{0}/{1}", onAccountOfWally, moniker);
...why would I need to also specify it in postData? Or could I set postData (if that's just a necessary step to get the length) to those values...something like:
postData = String.Format("{0}, {1}", onAccountOfWally, moniker);
?
To talk to ASP.NET Web API, you do not necessarily need the client library, although it makes the life easier. After all, one of the benefits of HTTP services is the platform reach. Literally you can use any library that gives you HTTP capabilities. So, using WebRequest, you can do something like this. I'm using JSON in the payload. You can use XML and application/www-form-urlencoded as well. Just that you need to format the request body accordingly. Also, for complex objects, you will be better off using JSON.NET unlike formatting the JSON manually.
var request = System.Net.WebRequest.Create("http://localhost:12345/api/values");
request.Method = "POST";
string postData = "{\"firstName\":\"Steven\"," + "\"lastName\":\"Waugh\"}";
byte[] byteArray = Encoding.UTF8.GetBytes(postData);
request.ContentType = "application/json";
request.ContentLength = byteArray.Length;
using (var requestStream = request.GetRequestStream())
{
requestStream.Write(byteArray, 0, byteArray.Length);
}
using (var response = request.GetResponse())
{
using (var responseStream = response.GetResponseStream())
{
using (var reader = new System.IO.StreamReader(responseStream))
{
string responseFromServer = reader.ReadToEnd();
Console.WriteLine(responseFromServer);
}
}
}
EDIT
If you are specifying data in URI, you do not need to specify the same in the request body. To let web API bind the parameters for you from URI, you will need to specify the route accordingly so that the placeholders are set for onAccountOfWally and moniker. Then you will need to use a simple type like string as action method parameters for web API to bind. By default, simple types are bound from URI path and query string and complex types from request body.