I am trying to send a post request, but when debugging I am receiving the AggregateException:
"An error occurred while copying content to a stream." with InnerException "Unable to read data from the transfer connection: the connection has been closed. An error occurred while copying content to a stream." - What could this mean?
Without debugging I get the error "Exception in HttpClientHandler - The request was aborted: a secure SSL/TLS channel could not be created...".
I am using IHttpClientFactory and the protocol is set to Tls12 which is correct one for me to use.
Using postman I can successfully send a post request with my client certificate.
I would appreciate it if anyone could point out flaws in my code and any possible ideas in order to correct the issue. Let me know if I need to be more clear or post more code. Thanks!
/// Payment.cs
private async Task<HttpResponseMessage> CreatePurchase()
{
PurchaseService purchaseService = new PurchaseService(context, repository, pm.CategoryCode);
var httpResponseMessage = await purchaseService .CreatePurchaseRequest(context);
return httpResponseMessage;
}
public async Task<HttpResponseMessage> CreatePurchaseRequest(Context context)
{
ServicePointManager.Expect100Continue = true;
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
endPoint = repository[$"PurchaseService/Endpoints/CreateSettlementOnlyPurchase"];
// Configure httpClient with IHttpClientFactory
HttpClientFactoryProvider httpClientFactoryProvider = new HttpClientFactoryProvider(context.Merchant.Test);
// Get client from ServiceCollection
var httpClient = httpClientFactoryProvider.GetClient("Purchase");
// Create settlePurchaseRequest object
SettlePurchaseRequest settlePurchaseRequest = CreateSettlePurchase(context);
// Serialize object into JSON
var purchaseRequest = settlePurchaseRequest.ToJson();
// Create digest
var payloadDigest = purchaseRequest != null ? Digest(purchaseRequest) : null;
Dictionary<string, string> signHeaderInfo = CreateSignHeadersInfo(HeaderDateName, now, "POST", targetURL + endPoint, payloadDigest);
// Wrap JSON inside a StringContent object
var content = new StringContent(purchaseRequest, Encoding.UTF8, "application/json");
// Post to the endpoint
var requestMessage = new HttpRequestMessage(HttpMethod.Post, endPoint);
requestMessage.Content = content;
requestMessage.Headers.Add(HeaderDateName, now);
var guid = Guid.NewGuid().ToString();
requestMessage.Headers.Add("X-Request-ID", guid);
var signature = CreateSignature(signHeaderInfo);
requestMessage.Headers.Add("Signature", signature);
requestMessage.Headers.Add("Digest", payloadDigest);
using (HttpResponseMessage httpResponseMessage = await httpClient.SendAsync(requestMessage, HttpCompletionOption.ResponseContentRead))
{
// Process response
httpResponseMessage.EnsureSuccessStatusCode();
var jsonString = httpResponseMessage.Content.ReadAsStringAsync().Result;
if (httpResponseMessage.IsSuccessStatusCode)
{
return httpResponseMessage;
}
else
{
return httpResponseMessage; // Todo: Refactor
}
}
}
private IHttpClientFactory HttpClientFactory()
{
if (_httpClientFactory != null)
{
return _httpClientFactory;
}
#region DI Service
var serviceCollection = new ServiceCollection();
#region Create Settlement Only Purchase
serviceCollection.AddHttpClient("Purchase", client =>
{
client.BaseAddress = new Uri(ApiURL);
client.DefaultRequestHeaders.Add("Api-Key", PurchaseApiKey);
client.DefaultRequestHeaders.Add("Accept", "*/*");
client.DefaultRequestHeaders.Add("Connection", "Keep-Alive");
client.DefaultRequestHeaders.Add("Keep-Alive", "3600");
client.DefaultRequestHeaders.Add("Host", hostName);
})
.ConfigurePrimaryHttpMessageHandler(() =>
{
var handler = new HttpClientHandler();
handler.ClientCertificateOptions = ClientCertificateOption.Manual;
handler.ClientCertificates.Add(GetCertificateBySerialNumber());
handler.SslProtocols = System.Security.Authentication.SslProtocols.Tls12;
//handler.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate;
return handler;
});
#endregion
var serviceProvider = serviceCollection.BuildServiceProvider();
_httpClientFactory = serviceProvider.GetService<IHttpClientFactory>();
return _httpClientFactory;
#endregion
public HttpClient GetClient(string clientName)
{
return HttpClientFactory().CreateClient(clientName);
}
Call stack with debugging
System.Net.Http.HttpRequestException: 'Fehler beim Kopieren von Inhalt in einen Stream.'
Inner Exception: IOException: Von der Übertragungsverbindung können keine Daten gelesen werden: Die Verbindung wurde geschlossen.
StackTrace: at System.Threading.Tasks.TaskFactory`1.FromAsyncCoreLogic(IAsyncResult iar, Func`2 endFunction, Action`1 endAction, Task`1 promise, Boolean requiresSynchronization)
Call stack without debugging
12.01.2023 14:25:04:5738|INFO|Service|23192|0| * ITAD-199497 * Exception Caught!
12.01.2023 14:25:04:5749|ERROR|Service|23192|Message :{0} | * ITAD-199497 * Fehler beim Senden der Anforderung.
12.01.2023 14:25:04:5749|INFO|TXMS.PaymentCapture|18768|0| * ITAD-199497 * Caught aggregate exception-Task.Wait behavior
12.01.2023 14:25:04:5749|ERROR|TXMS.PaymentCapture|18768|0| * ITAD-199497 * PayID: 95052fe0a86246569c23976899a20ced. Die Anfrage wurde abgebrochen: Es konnte kein geschützter SSL/TLS-Kanal erstellt werden..
12.01.2023 14:25:04:5749|VERBOSE|TXMS.PaymentCapture|18768|0| * ITAD-199497 * bei System.Net.HttpWebRequest.EndGetRequestStream(IAsyncResult asyncResult, TransportContext& context)
bei System.Net.Http.HttpClientHandler.GetRequestStreamCallback(IAsyncResult ar)
12.01.2023 14:25:04:5758|ERROR|ProcessList|18768|0| * ITAD-199497 * Common.TXMSException: Unhandled exception when calling the API.
Network Logs
System.Net.Http Error: 0 : [24128] HttpClient#56105527::SendAsync() - Fehler beim Senden von HttpRequestMessage#10319855. System.Net.Http.HttpRequestException: Fehler beim Kopieren von Inhalt in einen Stream. ---> System.IO.IOException: Von der Übertragungsverbindung können keine Daten gelesen werden: Die Verbindung wurde geschlossen.
bei System.Net.ConnectStream.EndWrite(IAsyncResult asyncResult)
bei System.Threading.Tasks.TaskFactory`1.FromAsyncCoreLogic(IAsyncResult iar, Func`2 endFunction, Action`1 endAction, Task`1 promise, Boolean requiresSynchronization)
--- Ende der internen Ausnahmestapelüberwachung ---
bei System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
bei System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
bei Microsoft.Extensions.Http.Logging.LoggingHttpMessageHandler.<SendAsync>d__5.MoveNext()
--- Ende der Stapelüberwachung vom vorhergehenden Ort, an dem die Ausnahme ausgelöst wurde ---
bei System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
bei System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
bei Microsoft.Extensions.Http.Logging.LoggingScopeHttpMessageHandler.<SendAsync>d__5.MoveNext()
ProcessId=4124
DateTime=2023-01-12T13:40:20.6476208Z
UPDATE: After disabling "Just My Code" and enabling all "Exception Settings", an System.Security.Authentication.AuthenticationException: The message received was unexpected or badly formatted". The general solution to this problem is check that the correct/latest TLS version is used and to check the cipher suites. Based off what I captured in Wireshark I see no TLS and Cipher issue. TLS1.2 and TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 are used.
The solution to my problem lied in my Windows Registry Editor.
What brought me to this was using the curl command. I provided my cert and key in pem format along with the full url of the post request.
A part of the error message I received was:
A quick Google turned up the following documentation about SChannel configuration: https://learn.microsoft.com/en-us/windows-server/security/tls/tls-registry-settings?tabs=diffie-hellman#messaging--fragment-parsing
Registry path: HKLM\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Messaging
To specify a maximum allowed size of fragmented TLS handshake messages that the TLS client will accept, create a MessageLimitClient entry. After you have created the entry, change the DWORD value to the desired bit length. If not configured, the default value will be 0x8000 bytes.
Once I added a MessageLimitClient, I was able to receive a response from my Post request.