aboutsummaryrefslogtreecommitdiff
path: root/client.go
diff options
context:
space:
mode:
authorDimitri Sokolyuk <demon@dim13.org>2015-12-31 14:38:27 +0100
committerDimitri Sokolyuk <demon@dim13.org>2015-12-31 14:38:27 +0100
commit69f81d8942d31e7ae9c8d25740e572d20638c5cd (patch)
tree5d6e9645f699d4b377dc8175d94db0e7b802afc9 /client.go
parent3e786c1c793ad6f2854a03431385b59dc4c27eae (diff)
Rename client into provider
Diffstat (limited to 'client.go')
-rw-r--r--client.go322
1 files changed, 0 insertions, 322 deletions
diff --git a/client.go b/client.go
deleted file mode 100644
index eae9f30..0000000
--- a/client.go
+++ /dev/null
@@ -1,322 +0,0 @@
-package acme
-
-import (
- "crypto/rsa"
- "crypto/x509"
- "encoding/json"
- "errors"
- "io/ioutil"
- "log"
- "net/http"
- "net/url"
- "regexp"
- "strconv"
- "time"
-
- "github.com/mgutz/ansi"
-)
-
-// Client ...
-type Client struct {
- Directory
- nonce chan string
-}
-
-var (
- errNoNonces = errors.New("out of nonces")
- errTimedOut = errors.New("timed out")
- errContentType = errors.New("unknown content type")
- errChallengeType = errors.New("unknown challenge")
-)
-
-// Nonce implements jose nonce provider
-func (c Client) Nonce() (string, error) {
- select {
- case nonce := <-c.nonce:
- return nonce, nil
- default:
- return "", errNoNonces
- }
-}
-
-func (c Client) replyNonce(r *http.Response) {
- if rn := r.Header.Get("Replay-Nonce"); rn != "" {
- c.nonce <- rn
- }
-}
-
-// NewClient fetches directory and initializes nonce
-func NewClient(directory string) (*Client, error) {
- c := &Client{nonce: make(chan string, 10)}
- resp, err := http.Get(directory)
- if err != nil {
- return nil, err
- }
- defer resp.Body.Close()
- defer c.replyNonce(resp)
- return c, json.NewDecoder(resp.Body).Decode(&c.Directory)
-}
-
-// Important header fields
-//
-// Replay-Nonce each response, required for next request
-// Link links to next stage
-// Retry-After polling interval
-// Location next step
-// Content-Location cert
-
-// Action Request Response
-//
-// Register POST new-reg 201 -> reg
-// Request challenges POST new-authz 201 -> authz
-// Answer challenges POST challenge 200
-// Poll for status GET authz 200
-// Request issuance POST new-cert 201 -> cert
-// Check for new cert GET cert 200
-
-// request is used for
-// new-reg, new-authz, challenge, new-cert
-func (c *Client) post(uri string, s Signer, v interface{}) (*http.Response, error) {
- body, err := json.Marshal(v)
- if err != nil {
- return nil, err
- }
- log.Println(ansi.Color("POST", "red+b"), uri, string(body))
-
- signed, err := s.Sign(body, c)
- if err != nil {
- return nil, err
- }
-
- resp, err := http.Post(uri, "application/json", signed)
- if err != nil {
- return nil, err
- }
- defer c.replyNonce(resp)
- log.Println(ansi.Color("STATUS", "yellow"), resp.Status)
-
- switch resp.Header.Get("Content-Type") {
- case "application/problem+json":
- defer resp.Body.Close()
- var p Problem
- if err = json.NewDecoder(resp.Body).Decode(&p); err != nil {
- return resp, err
- }
- if err, ok := urnErrors[p.Type]; ok {
- p.Err = err
- }
- return resp, p
- case "application/json":
- defer resp.Body.Close()
- return resp, json.NewDecoder(resp.Body).Decode(v)
- case "application/pkix-cert":
- return resp, nil
- default:
- return resp, errContentType
- }
-}
-
-type Links map[string]string
-
-type nextStep struct {
- Link Links
- Location *url.URL
- RetryAfter time.Duration
-}
-
-var linksRe = regexp.MustCompile(`^<(.*)>;rel="(.*)"`)
-
-func parseHeader(r *http.Response) nextStep {
- var ns nextStep
-
- if lo, err := r.Location(); err == nil {
- ns.Location = lo
- }
-
- ns.Link = make(Links)
- for _, li := range r.Header["Link"] {
- re := linksRe.FindStringSubmatch(li)
- if len(re) == 3 {
- ns.Link[re[2]] = re[1]
- }
- }
-
- if ra := r.Header.Get("Retry-After"); ra != "" {
- n, err := strconv.Atoi(ra)
- if err == nil {
- ns.RetryAfter = time.Second * time.Duration(n)
- }
- }
-
- log.Println(ansi.Color("NEXT", "cyan"), ns)
- return ns
-}
-
-/*
- directory
- .
- .
- ....................................................
- . . . .
- . . . .
- V "next" V "next" V V
- new-reg ---+----> new-authz ---+----> new-cert revoke-cert
- . | . | . ^
- . | . | . | "revoke"
- V | V | V |
- reg* ----+ authz -----+ cert-----------+
- . ^ |
- . | "up" | "up"
- V | V
- challenge cert-chain
-*/
-
-func (c *Client) Register(a *Account) error {
- r := &Registration{
- Resource: ResNewReg,
- Contact: a.contact,
- }
- resp, err := c.post(c.NewReg, a, r)
- if err != nil && err.(Problem).Err != ErrMalformed {
- return err
- }
- ns := parseHeader(resp)
- switch resp.StatusCode {
- case http.StatusConflict:
- // Query Location
- r = &Registration{Resource: ResRegister}
- resp, err = c.post(ns.Location.String(), a, r)
- if err != nil {
- return err
- }
- fallthrough
- case http.StatusCreated:
- // Agree to TOS
- if tos := ns.Link["terms-of-service"]; tos != "" {
- r = &Registration{
- Resource: ResRegister,
- Contact: a.contact,
- Agreement: tos,
- }
- _, err = c.post(ns.Location.String(), a, r)
- }
- }
- return err
-}
-
-func pickChallenge(c []Challenge) (int, Challenge) {
- for i, ch := range c {
- if canSolve[ch.Type] {
- return i, ch
- }
- }
- return -1, Challenge{}
-}
-
-func (c *Client) Authorize(s ThumbSigner, domain string) error {
- ident := Identifier{
- Type: IdentDNS,
- Value: domain,
- }
- r := &Authorization{
- Resource: ResNewAuthz,
- Identifier: ident,
- }
- resp, err := c.post(c.NewAuthz, s, r)
- if err != nil {
- return err
- }
- switch resp.StatusCode {
- case http.StatusCreated:
- n, ch := pickChallenge(r.Challenges)
- if n < 0 {
- return errors.New("can't solve any challenges")
- }
-
- ka, _ := KeyAuthorization(s, ch.Token)
- ans := &Challenge{
- Resource: ResChallenge,
- Type: ch.Type,
- KeyAuthorization: ka,
- }
- _, err = c.post(ch.URI, s, ans)
-
- var sol Solver
-
- switch ch.Type {
- case ChallengeHTTP:
- sol = &httpChallenge{Addr: "localhost:8080", Challenge: *ans}
- default:
- return errChallengeType
- }
-
- if err := Solve(sol, time.Minute); err != nil {
- return err
- }
-
- ns := parseHeader(resp)
- done := make(chan bool)
- errc := make(chan error)
- log.Println(ansi.Color("NEXT", "green"), ns)
- ticker := time.NewTicker(time.Second)
- defer ticker.Stop()
- go func() {
- for range ticker.C {
- err := c.queryStatus(ns.Location.String(), n, done)
- if err != nil {
- errc <- err
- return
- }
- }
- }()
- select {
- case <-done:
- case err = <-errc:
- case <-time.After(30 * time.Second):
- return errTimedOut
- }
-
- }
- return err
-}
-
-func (c *Client) queryStatus(url string, n int, done chan bool) error {
- r := &Authorization{}
- resp, err := http.Get(url)
- if err != nil {
- return err
- }
- defer resp.Body.Close()
- defer c.replyNonce(resp)
- err = json.NewDecoder(resp.Body).Decode(r)
- if err != nil {
- return err
- }
- if r.Challenges[n].Status == StatusValid {
- done <- true
- }
- return nil
-}
-
-func (c *Client) Cert(s Signer, altnames []string, key *rsa.PrivateKey) (*x509.Certificate, error) {
- csr, err := NewCSR(altnames, key)
- if err != nil {
- return nil, err
- }
- r := &CSR{
- Resource: ResNewCert,
- CSR: csr,
- }
- resp, err := c.post(c.NewCert, s, r)
- if err != nil {
- return nil, err
- }
- defer resp.Body.Close()
- ns := parseHeader(resp)
- log.Println(ansi.Color("NEXT", "green"), ns)
- der, err := ioutil.ReadAll(resp.Body)
- if err != nil {
- return nil, err
- }
- return x509.ParseCertificate(der)
-}