package acme import ( "bytes" "crypto" "encoding/json" "errors" "io/ioutil" "net/http" "regexp" "time" ) const ( // LE1 Let's Encrypt V1 LE1 = `https://acme-v01.api.letsencrypt.org/directory` // LES Let's Encrypt Staging LES = `https://acme-staging.api.letsencrypt.org/directory` ) type Resource string const ( ResNewReg Resource = "new-reg" ResNewAuthz Resource = "new-authz" ResNewCert Resource = "new-cert" ResRevokeCert Resource = "revoke-cert" ResReg Resource = "reg" ResAuthz Resource = "authz" ResChallenge Resource = "challenge" ResCert Resource = "cert" ) // Directory ... type Directory struct { NewReg string `json:"new-reg"` NewAuthz string `json:"new-authz"` NewCert string `json:"new-cert"` RevokeCert string `json:"revoke-cert"` Meta *Meta `json:"meta,omitemtpy"` } type Meta struct { TOS string `json:"terms-of-service"` Website string `json:"website"` CAA []string `json:"caa-identities"` } // Provider ... type Provider struct { Directory http.Client thumb string } var ( errContentType = errors.New("unknown content type") errChalType = errors.New("unknown challenge") errStatus = errors.New("unexpected status") ) const ( mimeJson = "application/json" mimeJose = "application/jose+json" mimeProblem = "application/problem+json" mimePkix = "application/pkix-cert" timeout = time.Second * 10 poll = time.Second ) // DialProvider fetches directory and initializes first nonce func DialProvider(directory string, key crypto.PrivateKey) (*Provider, error) { sig, err := newSigner(key) if err != nil { return nil, err } thumb, err := Thumbnail(key) if err != nil { return nil, err } p := &Provider{ Client: http.Client{ Transport: sig, }, thumb: thumb, } if directory == "" { directory = LE1 } resp, err := p.Get(directory) if err != nil { return nil, err } return p, parseJson(resp, &p.Directory) } func (p Provider) KeyAuth(token string) string { return token + "." + p.thumb } func (p *Provider) postJson(uri string, v interface{}) (*http.Response, error) { msg, err := json.Marshal(v) if err != nil { return nil, err } return p.Post(uri, mimeJose, bytes.NewReader(msg)) } type nextStep struct { Link map[string]string Location string } var linksRe = regexp.MustCompile(`^<(.*)>;rel="(.*)"`) func parseHeader(resp *http.Response) nextStep { var ns nextStep if lo, _ := resp.Location(); lo != nil { ns.Location = lo.String() } ns.Link = make(map[string]string) for _, li := range resp.Header["Link"] { re := linksRe.FindStringSubmatch(li) if len(re) == 3 { ns.Link[re[2]] = re[1] } } return ns } func parseJson(resp *http.Response, v interface{}) error { defer resp.Body.Close() switch resp.Header.Get("Content-Type") { case mimeJson: return json.NewDecoder(resp.Body).Decode(v) case mimeProblem: return problem(resp.Body) default: return errContentType } } func parseCert(resp *http.Response) ([]byte, error) { defer resp.Body.Close() switch resp.Header.Get("Content-Type") { case mimePkix: return ioutil.ReadAll(resp.Body) case mimeProblem: return nil, problem(resp.Body) default: return nil, errContentType } }