Split off validation function.

This is a loop that interacts with the ACME server, not the individual challenges.

Also switch to exponential back-off polling for good measure.
This commit is contained in:
Tommie Gannert 2015-12-05 11:51:30 +00:00
parent 237689b0cf
commit 58a2fd2267
3 changed files with 81 additions and 92 deletions

View file

@ -670,3 +670,82 @@ func parseLinks(links []string) map[string]string {
return linkMap return linkMap
} }
// validate makes the ACME server start validating a
// challenge response, only returning once it is done.
func validate(j *jws, uri string, chlng challenge) error {
var challengeResponse challenge
if err := postJSON(j, uri, chlng, &challengeResponse); err != nil {
return err
}
interval := 1 * time.Second
maxInterval := 15 * time.Minute
// After the path is sent, the ACME server will access our server.
// Repeatedly check the server for an updated status on our request.
for {
switch challengeResponse.Status {
case "valid":
logf("The server validated our request")
return nil
case "pending":
break
case "invalid":
return errors.New("The server could not validate our request.")
default:
return errors.New("The server returned an unexpected state.")
}
// Poll with exponential back-off.
time.Sleep(interval)
interval *= 2
if interval > maxInterval {
interval = maxInterval
}
if err := getJSON(uri, &challengeResponse); err != nil {
return err
}
}
return nil
}
// getJSON performs an HTTP GET request and parses the response body
// as JSON, into the provided respBody object.
func getJSON(uri string, respBody interface{}) error {
resp, err := http.Get(uri)
if err != nil {
return fmt.Errorf("failed to get %q: %v", uri, err)
}
defer resp.Body.Close()
if resp.StatusCode >= http.StatusBadRequest {
return handleHTTPError(resp)
}
return json.NewDecoder(resp.Body).Decode(respBody)
}
// postJSON performs an HTTP POST request and parses the response body
// as JSON, into the provided respBody object.
func postJSON(j *jws, uri string, reqBody, respBody interface{}) error {
jsonBytes, err := json.Marshal(reqBody)
if err != nil {
return errors.New("Failed to marshal network message...")
}
resp, err := j.post(uri, jsonBytes)
if err != nil {
return fmt.Errorf("Failed to post JWS message. -> %v", err)
}
defer resp.Body.Close()
if resp.StatusCode >= http.StatusBadRequest {
return handleHTTPError(resp)
}
return json.NewDecoder(resp.Body).Decode(respBody)
}

View file

@ -1,13 +1,10 @@
package acme package acme
import ( import (
"encoding/json"
"errors"
"fmt" "fmt"
"net" "net"
"net/http" "net/http"
"strings" "strings"
"time"
) )
type httpChallenge struct { type httpChallenge struct {
@ -47,49 +44,7 @@ func (s *httpChallenge) Solve(chlng challenge, domain string) error {
close(s.end) close(s.end)
}() }()
jsonBytes, err := json.Marshal(challenge{Resource: "challenge", Type: chlng.Type, Token: chlng.Token, KeyAuthorization: keyAuth}) return validate(s.jws, chlng.URI, 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("The server validated our request")
break Loop
case "pending":
break
case "invalid":
return errors.New("The server could not validate our request.")
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) { func (s *httpChallenge) startHTTPServer(domain string, token string, keyAuth string) {

View file

@ -5,12 +5,9 @@ import (
"crypto/sha256" "crypto/sha256"
"crypto/tls" "crypto/tls"
"encoding/hex" "encoding/hex"
"encoding/json"
"errors"
"fmt" "fmt"
"net" "net"
"net/http" "net/http"
"time"
) )
type tlsSNIChallenge struct { type tlsSNIChallenge struct {
@ -57,49 +54,7 @@ func (t *tlsSNIChallenge) Solve(chlng challenge, domain string) error {
close(t.end) close(t.end)
}() }()
jsonBytes, err := json.Marshal(challenge{Resource: "challenge", Type: chlng.Type, Token: chlng.Token, KeyAuthorization: keyAuth}) return validate(t.jws, chlng.URI, 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 TLS-SNI-01
resp, err := t.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("The server validated our request")
break Loop
case "pending":
break
case "invalid":
return errors.New("The server could not validate our request.")
default:
return errors.New("The server returned an unexpected state.")
}
time.Sleep(1 * time.Second)
resp, err = http.Get(chlng.URI)
}
return nil
} }
func (t *tlsSNIChallenge) generateCertificate(keyAuth string) (tls.Certificate, error) { func (t *tlsSNIChallenge) generateCertificate(keyAuth string) (tls.Certificate, error) {