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.

enter image description here

3

There are 3 answers

0
Colin Roe On BEST ANSWER

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.

curl  --verbose --cert fullchain.pem --key privKey.pem {RequestUri}

A part of the error message I received was:

* schannel: disabled automatic use of client certificate

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.

1
WerkmanW On

I understand your confusion, seeing the IDisposable interface being implemented on the HttpResponseMessage, but I don't think you need to dispose of it yourself. Looking at the class example usage here, you can see that you can simply use it however you want and exit the method without calling Dispose or holding it in a using statement. Can you try that and see if it works?

Additionally, I'm a bit confused by the way you implement the IHttpClientFactory pattern. Is dependency injection not an option for you? If it is, I'd suggest following the guidelines mentioned in this article and inject the IHttpClientFactory into your service that needs the HttpClient.

8
Mike Mozhaev On

You are returning disposable object from inside of using statement. So it's disposed before returning.

    using (HttpResponseMessage httpResponseMessage = await httpClient.SendAsync(requestMessage, HttpCompletionOption.ResponseContentRead))
    {
        // ...
        return httpResponseMessage;
    }

You need either to read whatever is needed from the response message inside using scope or remove using statement and dispose the message in the calling code, once it's fully processed.