Issues making certificates


#1

My domain is: www.exhibapi.dynu.net

I can login to a root shell on my machine: yes

im making a custom api framework for restfull http & websocket api’s, i’m making a function that will get a certificate for the server, but when ever i do this it fails even though i see that the acme server got the http-01 KeyData.

im making this in c# and im using Certes to call the api server to start the order

Accept Code: (c#)

if (ACMEChallenge != null)
    if (Req.RequestPath == $"/.well-known/acme-challenge/{ACMEChallenge.Token}")
    {
        Req.Requester.Writer.Write($"HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\n\r\n" + ACMEChallenge.KeyAuthz);
        Console.WriteLine("Wrote:" + $"HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\n\r\n{ACMEChallenge.KeyAuthz}");
        Console.WriteLine();
        ACME_Wait.Set();
        return;
    }

Request Function:

private Byte [ ] GetSSLCert(AcmeContext Context, String CertPassword, CsrInfo Info)
    {
        this.ACME_Wait = new ManualResetEvent(false);

        IOrderContext Order = Context.NewOrder(new String [ ] { this.DNS }).GetAwaiter().GetResult();
        IAuthorizationContext Auth = Order.Authorizations().GetAwaiter().GetResult().First();
        this.ACMEChallenge = Auth.Http().GetAwaiter().GetResult();
        Certes.Acme.Resource.Challenge Rest = this.ACMEChallenge.Validate().GetAwaiter().GetResult();
        this.ACME_Wait.WaitOne();
        Thread.Sleep(1000);
        IKey KEY = KeyFactory.NewKey(KeyAlgorithm.ES512);
        CertificateChain Cert = Order.Generate(Info, KEY).GetAwaiter().GetResult();
        //ACMEChallenge = null;
        return Cert.ToPfx(KEY).Build("BDHttpServerCertificate", CertPassword);
    }

Console Log:


Note: it says “RECIVED NULL!” when ever the connection handler recives null from the stream reader, indicating the connection got shutdown or closed.


#2

What was the result of the validation?

Can you link to the authz for one of your failed orders?


#3

https://acme-staging-v02.api.letsencrypt.org/acme/challenge/Su_8HYlHvqwZsXn3xaPMQTchhPfca7Fa8feN9oXY0fg/277764491
thats the latest one i’v tried


#5

Hi @dedady157

is your website online? There are only timeouts.


#6

it was offline, had to go to bed. ill set it up and leave it running today, but, acme got the data that was suposed to be on the site yet it failed


#7

There you see the problem. The content:

HTTP/1.1 200 OK
Date: Fri, 22 Mar 2019 15:34:30 UTC+1
Server: BD Http & Https API Server
Content-Length: 87
Content-Type: text/plain
Connection: Keep-Alive

aCp4zR1F7R64EOqKssZ3I0NYNPFfjgdtSeyZUg_BBuA.SmKxIwmqcAD__lHIcZcpyCS3hpg5ulcf6Zh_UVH-URQ

Your code sends the header lines as http content.

If you use something like

Req.Requester.Writer.Write($"HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\n\r\n" + ACMEChallenge.KeyAuthz);

that’s wrong. That’s the content of the file.

There should be something like

Response.Headers.ContentType = "text/plain"
Writer.Write(ACMEChallenge.KeyAuthz)

#8

ah so the content of the token should be within the header?
IE: “HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\n{other headers…}\r\n{Token}”

this is the responce writer’s code:

internal void WriteResponce()
    {
        if (this.Sent)
            throw new InvalidOperationException();
        lock (this) // Lock Self
        {
            if (!this.Sent)
            {
                this.Sent = true;
                this.FillHeaders();
                this.Connection.Writer.Write($"HTTP/1.1 {(UInt16)this.Code} {this.Code.ToString().Replace('_', ' ')}\r\n");
                foreach (String K in this.Headers.Keys)
                    this.Connection.Writer.Write($"{K}: {this.Headers [ K ]}\r\n");
                this.Connection.Writer.Write("\r\n");
                if (this.Responce != null)
                    this.Connection.Writer.Write(this.Responce);

                this.Connection.Writer.Flush();
            }
        }
    }

#9

No. The file content is only the token.

Your code duplicates the Content-Type header in your document. Then another instance adds the date, Server and connection header.

Normally, you don’t write a http/1.1 header with a 200 OK.


#10

no, that was when i was writting it directly, now it’s writting it via the normal responce handler.

this is how the responce handler fills in the default headers:

private void FillHeaders()
    {
        DateTime Day = DateTime.Now;
        this.Headers.Add("Date", $"{Day.DayOfWeek.ToString().Substring(0, 3)}, {Day.Day} {((MonthsOfYear)Day.Month).ToString().Substring(0, 3)} {Day.Year} {Day.ToLongTimeString()} {((TimeZoneInfo.Local.BaseUtcOffset.Hours != 0) ? "UTC+" + TimeZoneInfo.Local.BaseUtcOffset.Hours : "UTC")}");
        this.Headers.Add("Server", "BD Http & Https API Server");
        this.Headers.Add("Content-Length", (this.Responce == null) ? "0" : Encoding.UTF8.GetByteCount(this.Responce).ToString());
        this.Headers.Add("Content-Type", this.ContentType);
        if (!this.Headers.ContainsKey("Connection"))
            this.Headers.Add("Connection", (this.Code == HttpResponceCodes.Switching_Protocols) ? "Upgrade" : "Keep-Alive");
    }

#11


for me its sending the data correctly, and there isnt any headers within the content/message body


#12

It’s wrong.

Check the url

http://www.exhibapi.dynu.net/.well-known/acme-challenge/aCp4zR1F7R64EOqKssZ3I0NYNPFfjgdtSeyZUg_BBuA

in your browser. You see the header rows in your browser.


#13


im not seeing it

Ps: since i restarted the server it tryed to do the challenge agen.
challenge uri:http://www.exhibapi.dynu.net/.well-known/acme-challenge/M3i5BBl5Ypu7_2xTfUwGISXLrMw5dMq8xxr7UwiPMnI


#14

I don’t see Html, instead I see the raw content:

HTTP/1.1 404 Not Found
Date: Fri, 22 Mar 2019 16:42:26 UTC+1
Server: BD Http & Https API Server
Content-Length: 160
Content-Type: text/html
Connection: Keep-Alive

<html><head><title>404 Not Found</title></head><body><h1>Not Found</h1><p>The requested URL was not found on this server.</p><br><p>Using Http</p></body></html>

#15

yea thats a valid http/1.1 responce so, i dont get why its not working…

do you mean i have to build in an exception that just shoves out the responce, not incased within a http/1.1 responce body??

if so, how the heck is this supposed to work with a webserver on the usual basis???


#16

And that’s wrong.

The .NET HttpWebResponse object has some standard header properties. Use these instead of creating own header entries.


#17

everything is directly written to the tcp steam, it never touches anything else. and i don’t want to have to figure out a way of working around something that wasn’t built for the porpose at hand. like i said in the main post, this is a custom api server, everything other then certes is self contained, from the connection handler to the request parser and responce writter.

what header is missing i can easily add it in
im guessing its the ‘Content-Encoding’ header?


#18

image
this is just for getting responces from an outgoing http request, i cant use it here, since i need to be the one creating them


#19

But you write it too late, the real headers already sent.

So it’s content, not header-content.


#20

noooooo, look i accept a tcp stream, if the stream sends me a valid http request type first 7bytes i hand it over to the http connection handler, witch will then go ahead and unpack the request, it will then send that request up to the main server container witch will then processs the request, if it is a predefined reqeust such as the acme-challange it will write the responce to the RAW tcp connection, there is NOTHING on top of it, other then the networkstream witch DOESN’T write to the tcp connection, it mearly acts as a data helper above the raw tcp socket. so there is in no way anything being sent to the tcp connection other then what im sending it, there is no duplication of the headers or anything like that since i have to send them, and nothing will ever automaticly handle it outside the http api server framework, witch is “me”

ill post the code for handling http connections, and show you the path witch the request takes


#21

Http Handler:

private void WorkThread()
    {
        while (this.BaseSoc.Connected && !this.IsWebSocket)
        {
            Dictionary<String, String> Headers = new Dictionary<String, String>();
            String NextLine;
            String [ ] Segments;
            String Path;
            HTTPRequestTypes Type;
            try
            {
                NextLine = this.Reader.ReadLine();

                if (NextLine != null)
                {
                    // Parse Request Head
                    Segments = NextLine.Split(' ');
                    Type = Enum.Parse<HTTPRequestTypes>(Segments [ 0 ]);
                    Path = Segments [ 1 ];
                    // Read Headers
                    NextLine = this.Reader.ReadLine();
                    while (NextLine != "")
                    {
                        Segments = NextLine.Split(':');
                        Headers.Add(Segments [ 0 ], Segments [ 1 ].TrimStart(' '));
                        NextLine = this.Reader.ReadLine();
                    }

                    // Get PostData if any
                    if (Type == HTTPRequestTypes.POST)
                    {
                        if (!Headers.ContainsKey("Content-Length"))
                        {
                            Console.WriteLine("POST WITH NO CONTENT-LENGTH");
                            HttpResponce Res = new HttpResponce(this)
                            {
                                Code = HttpResponceCodes.Bad_Request
                            };
                            Res.WriteResponce();
                            continue;
                        }
                        Char [ ] PostData = new Char [ Int64.Parse(Headers [ "Content-Length" ]) ];
                        this.Reader.ReadBlock(PostData);
                        this.Parent.Processes(new HttpRequest(Type, Path, Headers, new String(PostData), this));
                    }
                    else
                        this.Parent.Processes(new HttpRequest(Type, Path, Headers, this));
                }
                else
                {
                    Console.WriteLine("RECIVED NULL!");
                    this.Dispose();
                }
            }
            catch (IOException)
            {
                this.Dispose();
            }
            catch (Exception E)
            {
                Console.WriteLine("EXCEPTION WHILE ACCEPTING:" + E.ToString());
                try
                {
                    // BAD REQ
                    HttpResponce Res = new HttpResponce(this)
                    {
                        Code = HttpResponceCodes.Bad_Request,
                    };
                    Res.Headers.Add("ErrorCSType", E.GetType().FullName);
                    Res.WriteResponce();
                }
                catch { this.Dispose(); }
            }
        }
        if (this.IsWebSocket)// Saifty Switch
            this.WebSocketHandle();
    }

Reqeust Processer:

internal void Processes(HttpRequest Req)
    {
        OnHttpRequest?.Invoke(new HttpRequestInfo(Req));
        HttpResponce Res = Req.GetResponce();

        // Check if Request is WebSocket, if so, diff handling
        if (Req.Headers.ContainsKey("Upgrade"))
            if (Req.Headers [ "Upgrade" ] == "websocket")
            {
                lock (this.Routes)
                    foreach (HttpRouteInfo I in this.Routes)
                        if (I.Type == HTTPRequestTypes.WebSocket && I.Route.Validate(Req.RequestPath))
                        {
                            try
                            {
                                I.WsCallback.Invoke(new HttpWebSocketContext(Req, Res));
                            }
                            catch (Exception E)
                            {
                                Res.Code = HttpResponceCodes.Internal_Server_Error;
                                if (this.ReturnExceptionDetails)
                                    Res.Responce = E.ToString();
                                else
                                    Res.Responce = null;
                            }
                            Res.WriteResponce();
                            return;
                        }
                // 404 !
                Res.Code = HttpResponceCodes.Not_Found;
                Res.Responce = $"<html><head><title>404 Not Found</title></head><body><h1>Not Found</h1><p>The requested URL was not found on this server.</p><br><p>Using {(((Req.Secure) ? "Https" : "Http"))}</p></body></html>";
                Res.WriteResponce();
                return;
            }

        if (this.ACMEChallenge != null)
            if (Req.RequestPath == $"/.well-known/acme-challenge/{this.ACMEChallenge.Token}")
            {
                Res.Code = HttpResponceCodes.OK;
                Res.ContentType = "text/plain";
                Res.Responce = ACMEChallenge.KeyAuthz;
                Res.WriteResponce();
                this.ACME_Wait.Set();
                return;
            }

        {

            lock (this.Routes)
                foreach (HttpRouteInfo I in this.Routes)
                    if (I.Type == Req.RequestType && I.Route.Validate(Req.RequestPath))
                    {
                        try
                        {
                            I.Callback.Invoke(Req, Res);
                        }
                        catch (Exception E)
                        {
                            Res.Code = HttpResponceCodes.Internal_Server_Error;
                            if (this.ReturnExceptionDetails)
                                Res.Responce = E.ToString();
                            else
                                Res.Responce = null;
                        }
                        Res.WriteResponce();
                        return;
                    }
        }
        // 404 !
        Res.Code = HttpResponceCodes.Not_Found;
        Res.Responce = $"<html><head><title>404 Not Found</title></head><body><h1>Not Found</h1><p>The requested URL was not found on this server.</p><br><p>Using {(((Req.Secure) ? "Https" : "Http"))}</p></body></html>";
        Res.WriteResponce();
    }

Responce Writter (in one function):

internal void WriteResponce()
    {
        if (this.Sent)
            throw new InvalidOperationException();
        lock (this) // Lock Self
        {
            if (!this.Sent)
            {
                this.Sent = true;
                DateTime Day = DateTime.Now;
                this.Headers.Add("Date", $"{Day.DayOfWeek.ToString().Substring(0, 3)}, {Day.Day} {((MonthsOfYear)Day.Month).ToString().Substring(0, 3)} {Day.Year} {Day.ToLongTimeString()} {((TimeZoneInfo.Local.BaseUtcOffset.Hours != 0) ? "UTC+" + TimeZoneInfo.Local.BaseUtcOffset.Hours : "UTC")}");
                this.Headers.Add("Server", "BD Http & Https API Server");
                this.Headers.Add("Content-Length", (this.Responce == null) ? "0" : Encoding.UTF8.GetByteCount(this.Responce).ToString());
                this.Headers.Add("Content-Type", this.ContentType);
                if (!this.Headers.ContainsKey("Connection"))
                    this.Headers.Add("Connection", (this.Code == HttpResponceCodes.Switching_Protocols) ? "Upgrade" : "Keep-Alive");
                
                this.Connection.Writer.Write($"HTTP/1.1 {(UInt16)this.Code} {this.Code.ToString().Replace('_', ' ')}\r\n");
                foreach (String K in this.Headers.Keys)
                    this.Connection.Writer.Write($"{K}: {this.Headers [ K ]}\r\n");
                this.Connection.Writer.Write("\r\n");
                if (Data != null)
                    this.Connection.Writer.Write(this.Responce);

                this.Connection.Writer.Flush();
            }
        }
    }