Skip to content

Commit

Permalink
Clean some stuff up and refactor getCerts for some concurrency.
Browse files Browse the repository at this point in the history
  • Loading branch information
xenolf committed Oct 18, 2015
1 parent 62b4ebe commit caa6e78
Show file tree
Hide file tree
Showing 3 changed files with 97 additions and 35 deletions.
113 changes: 87 additions & 26 deletions acme/client.go
Expand Up @@ -11,7 +11,9 @@ import (
"net/http"
"os"
"regexp"
"strconv"
"strings"
"time"
)

// Logger is used to log errors; if nil, the default log.Logger is used.
Expand Down Expand Up @@ -163,6 +165,7 @@ func (c *Client) ObtainCertificates(domains []string) ([]CertificateResource, er
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 {
encodedCert := base64.URLEncoding.EncodeToString(certificate)

Expand Down Expand Up @@ -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
// granted certificates.
func (c *Client) requestCertificates(challenges []*authorizationResource) ([]CertificateResource, error) {
var certs []CertificateResource
resc, errc := make(chan CertificateResource), make(chan error)
for _, authz := range challenges {
privKey, err := generatePrivateKey(c.keyBits)
if err != nil {
return nil, err
}
go c.requestCertificate(authz, resc, errc)
}

csr, err := generateCsr(privKey, authz.Domain)
if err != nil {
return nil, err
}
csrString := base64.URLEncoding.EncodeToString(csr)
jsonBytes, err := json.Marshal(csrMessage{Resource: "new-cert", Csr: csrString, Authorizations: []string{authz.AuthURL}})
if err != nil {
return nil, err
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)
}
}

resp, err := c.jws.post(authz.NewCertURL, jsonBytes)
if err != nil {
return nil, err
}
return certs, nil
}

func (c *Client) requestCertificate(authz *authorizationResource, result chan CertificateResource, errc chan error) {
privKey, err := generatePrivateKey(c.keyBits)
if err != nil {
errc <- err
return
}

csr, err := generateCsr(privKey, authz.Domain)
if err != nil {
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")
csrString := base64.URLEncoding.EncodeToString(csr)
jsonBytes, err := json.Marshal(csrMessage{Resource: "new-cert", Csr: csrString, Authorizations: []string{authz.AuthURL}})
if err != nil {
errc <- err
return
}

resp, err := c.jws.post(authz.NewCertURL, jsonBytes)
if err != nil {
errc <- err
return
}

privateKeyPem := pemEncode(privKey)
cerRes := CertificateResource{
Domain: authz.Domain,
CertURL: resp.Header.Get("Location"),
PrivateKey: privateKeyPem}

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)

This comment has been minimized.

Copy link
@mholt

mholt Oct 18, 2015

Contributor

Isn't Retry-After in seconds? (Not milliseconds)

}
break
default:
logger().Fatalf("[%s] The server returned an unexpected status code %d.", authz.Domain, resp.StatusCode)
return
}

cert, err := ioutil.ReadAll(resp.Body)
resp, err = http.Get(cerRes.CertURL)
if err != nil {
return nil, err
errc <- err
return
}

privateKeyPem := pemEncode(privKey)

certs = append(certs, CertificateResource{Domain: authz.Domain, CertURL: resp.Header.Get("Location"), PrivateKey: privateKeyPem, Certificate: cert})
}
return certs, nil
}

func logResponseHeaders(resp *http.Response) {
Expand Down
11 changes: 6 additions & 5 deletions acme/messages.go
Expand Up @@ -66,7 +66,7 @@ type challenge struct {
Status string `json:"status,omitempty"`
URI string `json:"uri,omitempty"`
Token string `json:"token,omitempty"`
Tls bool `json:"tls,omitempty"`
TLS bool `json:"tls,omitempty"`
}

type csrMessage struct {
Expand All @@ -84,8 +84,9 @@ type revokeCertMessage struct {
// PrivateKey and Certificate are both already PEM encoded
// and can be directly written to disk.
type CertificateResource struct {
Domain string
CertURL string
PrivateKey []byte
Certificate []byte
Domain string
CertURL string
CertStableURL string
PrivateKey []byte
Certificate []byte
}
8 changes: 4 additions & 4 deletions acme/simple_http_challenge.go
Expand Up @@ -147,17 +147,17 @@ func (s *simpleHTTPChallenge) startHTTPSServer(domain string, token string) (net
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 {
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)
if err != nil {
return nil, errors.New("startHTTPSServer: Failed to sign message...")
return nil, errors.New("startHTTPSServer: Failed to sign message")
}
signedCompact := signed.FullSerialize()
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.
Expand Down

0 comments on commit caa6e78

Please sign in to comment.