Clean some stuff up and refactor getCerts for some concurrency.

This commit is contained in:
xenolf 2015-10-18 02:16:15 +02:00
parent 62b4ebe72b
commit caa6e78289
3 changed files with 104 additions and 42 deletions

View file

@ -11,7 +11,9 @@ import (
"net/http" "net/http"
"os" "os"
"regexp" "regexp"
"strconv"
"strings" "strings"
"time"
) )
// Logger is used to log errors; if nil, the default log.Logger is used. // Logger is used to log errors; if nil, the default log.Logger is used.
@ -163,6 +165,7 @@ func (c *Client) ObtainCertificates(domains []string) ([]CertificateResource, er
return c.requestCertificates(challenges) return c.requestCertificates(challenges)
} }
// RevokeCertificate takes a DER encoded certificate and tries to revoke it at the CA.
func (c *Client) RevokeCertificate(certificate []byte) error { func (c *Client) RevokeCertificate(certificate []byte) error {
encodedCert := base64.URLEncoding.EncodeToString(certificate) encodedCert := base64.URLEncoding.EncodeToString(certificate)
@ -287,42 +290,100 @@ func (c *Client) getChallenges(domains []string) []*authorizationResource {
// It then uses these to request a certificate from the CA and returns the list of successfully // It then uses these to request a certificate from the CA and returns the list of successfully
// granted certificates. // granted certificates.
func (c *Client) requestCertificates(challenges []*authorizationResource) ([]CertificateResource, error) { func (c *Client) requestCertificates(challenges []*authorizationResource) ([]CertificateResource, error) {
var certs []CertificateResource resc, errc := make(chan CertificateResource), make(chan error)
for _, authz := range challenges { for _, authz := range challenges {
go c.requestCertificate(authz, resc, errc)
}
var certs []CertificateResource
for i := 0; i < len(challenges); i++ {
select {
case res := <-resc:
certs = append(certs, res)
case err := <-errc:
logger().Printf("%v", err)
}
}
return certs, nil
}
func (c *Client) requestCertificate(authz *authorizationResource, result chan CertificateResource, errc chan error) {
privKey, err := generatePrivateKey(c.keyBits) privKey, err := generatePrivateKey(c.keyBits)
if err != nil { if err != nil {
return nil, err errc <- err
return
} }
csr, err := generateCsr(privKey, authz.Domain) csr, err := generateCsr(privKey, authz.Domain)
if err != nil { if err != nil {
return nil, err errc <- err
return
} }
csrString := base64.URLEncoding.EncodeToString(csr) csrString := base64.URLEncoding.EncodeToString(csr)
jsonBytes, err := json.Marshal(csrMessage{Resource: "new-cert", Csr: csrString, Authorizations: []string{authz.AuthURL}}) jsonBytes, err := json.Marshal(csrMessage{Resource: "new-cert", Csr: csrString, Authorizations: []string{authz.AuthURL}})
if err != nil { if err != nil {
return nil, err errc <- err
return
} }
resp, err := c.jws.post(authz.NewCertURL, jsonBytes) resp, err := c.jws.post(authz.NewCertURL, jsonBytes)
if err != nil { if err != nil {
return nil, err errc <- err
} return
if resp.Header.Get("Content-Type") != "application/pkix-cert" {
return nil, fmt.Errorf("The server returned an unexpected content-type header: %s - expected %s", resp.Header.Get("Content-Type"), "application/pkix-cert")
}
cert, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
} }
privateKeyPem := pemEncode(privKey) privateKeyPem := pemEncode(privKey)
cerRes := CertificateResource{
Domain: authz.Domain,
CertURL: resp.Header.Get("Location"),
PrivateKey: privateKeyPem}
certs = append(certs, CertificateResource{Domain: authz.Domain, CertURL: resp.Header.Get("Location"), PrivateKey: privateKeyPem, Certificate: cert}) for {
switch resp.StatusCode {
case 202:
case 201:
cert, err := ioutil.ReadAll(resp.Body)
if err != nil {
errc <- err
return
}
// The server returns a body with a length of zero if the
// certificate was not ready at the time this request completed.
// Otherwise the body is the certificate.
if len(cert) > 0 {
cerRes.CertStableURL = resp.Header.Get("Content-Location")
cerRes.Certificate = cert
result <- cerRes
} else {
// The certificate was granted but is not yet issued.
// Check retry-after and loop.
ra := resp.Header.Get("Retry-After")
retryAfter, err := strconv.Atoi(ra)
if err != nil {
errc <- err
return
}
logger().Printf("[%s] Server responded with status 202. Respecting retry-after of: %d", authz.Domain, retryAfter)
time.Sleep(time.Duration(retryAfter) * time.Millisecond)
}
break
default:
logger().Fatalf("[%s] The server returned an unexpected status code %d.", authz.Domain, resp.StatusCode)
return
}
resp, err = http.Get(cerRes.CertURL)
if err != nil {
errc <- err
return
}
} }
return certs, nil
} }
func logResponseHeaders(resp *http.Response) { func logResponseHeaders(resp *http.Response) {

View file

@ -66,7 +66,7 @@ type challenge struct {
Status string `json:"status,omitempty"` Status string `json:"status,omitempty"`
URI string `json:"uri,omitempty"` URI string `json:"uri,omitempty"`
Token string `json:"token,omitempty"` Token string `json:"token,omitempty"`
Tls bool `json:"tls,omitempty"` TLS bool `json:"tls,omitempty"`
} }
type csrMessage struct { type csrMessage struct {
@ -86,6 +86,7 @@ type revokeCertMessage struct {
type CertificateResource struct { type CertificateResource struct {
Domain string Domain string
CertURL string CertURL string
CertStableURL string
PrivateKey []byte PrivateKey []byte
Certificate []byte Certificate []byte
} }

View file

@ -147,17 +147,17 @@ func (s *simpleHTTPChallenge) startHTTPSServer(domain string, token string) (net
return nil, fmt.Errorf("Could not start HTTP listener! -> %v", err) return nil, fmt.Errorf("Could not start HTTP listener! -> %v", err)
} }
jsonBytes, err := json.Marshal(challenge{Type: "simpleHttp", Token: token, Tls: true}) jsonBytes, err := json.Marshal(challenge{Type: "simpleHttp", Token: token, TLS: true})
if err != nil { if err != nil {
return nil, errors.New("startHTTPSServer: Failed to marshal network message...") return nil, errors.New("startHTTPSServer: Failed to marshal network message")
} }
signed, err := s.jws.signContent(jsonBytes) signed, err := s.jws.signContent(jsonBytes)
if err != nil { if err != nil {
return nil, errors.New("startHTTPSServer: Failed to sign message...") return nil, errors.New("startHTTPSServer: Failed to sign message")
} }
signedCompact := signed.FullSerialize() signedCompact := signed.FullSerialize()
if err != nil { if err != nil {
return nil, errors.New("startHTTPSServer: Failed to serialize message...") return nil, errors.New("startHTTPSServer: Failed to serialize message")
} }
// The handler validates the HOST header and request type. // The handler validates the HOST header and request type.