forked from go-acme/lego
-
Notifications
You must be signed in to change notification settings - Fork 0
/
http_challenge.go
133 lines (111 loc) · 3.15 KB
/
http_challenge.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
package acme
import (
"encoding/json"
"errors"
"fmt"
"net"
"net/http"
"strings"
"time"
)
type httpChallenge struct {
jws *jws
optPort string
start chan net.Listener
end chan error
}
func (s *httpChallenge) Solve(chlng challenge, domain string) error {
logf("[INFO][%s] acme: Trying to solve HTTP-01", domain)
s.start = make(chan net.Listener)
s.end = make(chan error)
// Generate the Key Authorization for the challenge
keyAuth, err := getKeyAuthorization(chlng.Token, &s.jws.privKey.PublicKey)
if err != nil {
return err
}
go s.startHTTPServer(domain, chlng.Token, keyAuth)
var listener net.Listener
select {
case listener = <-s.start:
break
case err := <-s.end:
return fmt.Errorf("Could not start HTTP server for challenge -> %v", err)
}
// Make sure we properly close the HTTP server before we return
defer func() {
listener.Close()
err = <-s.end
close(s.start)
close(s.end)
}()
jsonBytes, err := json.Marshal(challenge{Resource: "challenge", Type: chlng.Type, Token: chlng.Token, KeyAuthorization: keyAuth})
if err != nil {
return errors.New("Failed to marshal network message...")
}
// Tell the server we handle HTTP-01
resp, err := s.jws.post(chlng.URI, jsonBytes)
if err != nil {
return fmt.Errorf("Failed to post JWS message. -> %v", err)
}
// After the path is sent, the ACME server will access our server.
// Repeatedly check the server for an updated status on our request.
var challengeResponse challenge
Loop:
for {
if resp.StatusCode >= http.StatusBadRequest {
return handleHTTPError(resp)
}
err = json.NewDecoder(resp.Body).Decode(&challengeResponse)
resp.Body.Close()
if err != nil {
return err
}
switch challengeResponse.Status {
case "valid":
logf("[INFO][%s] The server validated our request", domain)
break Loop
case "pending":
break
case "invalid":
return handleChallengeError(challengeResponse)
default:
return errors.New("The server returned an unexpected state.")
}
time.Sleep(1 * time.Second)
resp, err = http.Get(chlng.URI)
}
return nil
}
func (s *httpChallenge) startHTTPServer(domain string, token string, keyAuth string) {
// Allow for CLI port override
port := ":80"
if s.optPort != "" {
port = ":" + s.optPort
}
listener, err := net.Listen("tcp", domain+port)
if err != nil {
// if the domain:port bind failed, fall back to :port bind and try that instead.
listener, err = net.Listen("tcp", port)
if err != nil {
s.end <- err
}
}
// Signal successfull start
s.start <- listener
path := "/.well-known/acme-challenge/" + token
// The handler validates the HOST header and request type.
// For validation it then writes the token the server returned with the challenge
http.HandleFunc(path, func(w http.ResponseWriter, r *http.Request) {
if strings.HasPrefix(r.Host, domain) && r.Method == "GET" {
w.Header().Add("Content-Type", "text/plain")
w.Write([]byte(keyAuth))
logf("[INFO] Served key authentication")
} else {
logf("[INFO] Received request for domain %s with method %s", r.Host, r.Method)
w.Write([]byte("TEST"))
}
})
http.Serve(listener, nil)
// Signal that the server was shut down
s.end <- nil
}