From c49a0bf48ea10b54dbac3480716d9786a0ce8411 Mon Sep 17 00:00:00 2001 From: Dimitri Sokolyuk Date: Sun, 31 Jan 2016 23:15:27 +0100 Subject: Split files (experimental) --- authorize.go | 79 ++++++++++++++++++++++++ certificate.go | 42 +++++++++++++ challenge.go | 90 +++++++++++++++++++++++++++ messages.go | 143 ------------------------------------------- provider.go | 188 +++++++++++++-------------------------------------------- register.go | 48 +++++++++++++++ 6 files changed, 301 insertions(+), 289 deletions(-) create mode 100644 authorize.go create mode 100644 certificate.go create mode 100644 challenge.go delete mode 100644 messages.go create mode 100644 register.go diff --git a/authorize.go b/authorize.go new file mode 100644 index 0000000..0dc82a2 --- /dev/null +++ b/authorize.go @@ -0,0 +1,79 @@ +package acme + +import "time" + +// Authorization request +type Authorization struct { + Resource Resource `json:"resource"` // new-authz + Identifier Identifier `json:"identifier"` + Status Status `json:"status,omitempty"` // e.g. valid + Expires *time.Time `json:"expires,omitempty"` + Challenges []Challenge `json:"challenges,omitempty"` + Combinations [][]int `json:"combinations,omitempty"` +} + +// Identifier ... +type Identifier struct { + Type IdentType `json:"type"` // dns + Value string `json:"value"` // example.com +} + +type IdentType string + +const IdentDNS IdentType = "dns" + +func (a Authorization) Supported(sol map[ChalType]Solver) []Challenge { + supported := func(com []int) bool { + for _, n := range com { + if _, ok := sol[a.Challenges[n].Type]; !ok { + return false + } + } + return true + } + for _, com := range a.Combinations { + if supported(com) { + c := make([]Challenge, len(com)) + for i, n := range com { + c[i] = a.Challenges[n] + } + return c + } + } + return nil +} + +func (p *Provider) authz(s Signer, domain string, sol map[ChalType]Solver) error { + // first step: pocke + req := &Authorization{ + Resource: ResNewAuthz, + Identifier: Identifier{ + Type: IdentDNS, + Value: domain, + }, + } + resp, err := p.post(p.NewAuthz, s, req) + if err != nil { + return err + } + err = parseJson(resp, req) + if err != nil { + return err + } + // second step: choose and start solver + for _, ch := range req.Supported(sol) { + if err = p.solve(s, ch, sol[ch.Type]); err != nil { + return err + } + } + return nil +} + +func (p *Provider) Authorize(s Signer, d *Desire) error { + for _, domain := range d.altnames { + if err := p.authz(s, domain, d.solver); err != nil { + return err + } + } + return nil +} diff --git a/certificate.go b/certificate.go new file mode 100644 index 0000000..4421061 --- /dev/null +++ b/certificate.go @@ -0,0 +1,42 @@ +package acme + +import "crypto/x509" + +type CSR struct { + Resource Resource `json:"resource"` // new-cert + CSR string `json:"csr"` +} + +func (p *Provider) Bundle(s Signer, d *Desire) error { + d.cert = make([]*x509.Certificate, 2) + // first step: post csr + csr, err := d.CSR() + if err != nil { + return err + } + req := &CSR{ + Resource: ResNewCert, + CSR: csr, + } + resp, err := p.post(p.NewCert, s, req) + if err != nil { + return err + } + d.cert[0], err = parseCert(resp) + if err != nil { + return err + } + ns := parseHeader(resp) + + // second step: cet CA + resp, err = p.Get(ns.Link["up"]) + if err != nil { + return err + } + d.cert[1], err = parseCert(resp) + if err != nil { + return err + } + + return nil +} diff --git a/challenge.go b/challenge.go new file mode 100644 index 0000000..c39bc12 --- /dev/null +++ b/challenge.go @@ -0,0 +1,90 @@ +package acme + +import ( + "log" + "time" +) + +// Challege ... +type Challenge struct { + Resource Resource `json:"resource"` // challenge + Type ChalType `json:"type"` + Token string `json:"token,omitempty"` + Status Status `json:"status,omitempty"` // e.g. valid + URI string `json:"uri,omitempty"` + Validated *time.Time `json:"validated,omitempty"` + KeyAuthorization string `json:"keyAuthorization,omitempty"` + Err *Problem `json:"error,omitempty"` +} + +// Status of request +type Status string + +// Statuses +const ( + StatusUnknown Status = "unknown" + StatusPending Status = "pending" + StatusProcessing Status = "processing" + StatusValid Status = "valid" + StatusInvalid Status = "invalid" + StatusRevoked Status = "revoked" +) + +type ChalType string + +const ( + ChallengeHTTP ChalType = "http-01" + ChallengeTLS ChalType = "tls-sni-01" + ChallengePOP ChalType = "proofOfPossession-01" + ChallengeDNS ChalType = "dns-01" +) + +func (p *Provider) solve(s Signer, ch Challenge, sol Solver) error { + ka, err := s.KeyAuth(ch.Token) + if err != nil { + return err + } + + // prepare solver + err = sol.Solve(ch.Token, ka) + if err != nil { + return err + } + defer sol.Solved() + + // update challenge + ch.Resource = ResChallenge + ch.KeyAuthorization = ka + + resp, err := p.post(ch.URI, s, ch) + if err != nil { + return err + } + ns := parseHeader(resp) + + return p.pollStatus(ns.Location) +} + +func (p *Provider) pollStatus(uri string) error { + t := time.NewTicker(poll) + defer t.Stop() + for range t.C { + resp, err := p.Get(uri) + if err != nil { + return err + } + req := new(Challenge) + err = parseJson(resp, req) + if err != nil { + return err + } + if req.Err != nil { + return req.Err + } + log.Println("status", req.Status) + if req.Status == StatusValid { + return nil + } + } + return nil +} diff --git a/messages.go b/messages.go deleted file mode 100644 index c462c1d..0000000 --- a/messages.go +++ /dev/null @@ -1,143 +0,0 @@ -package acme - -import ( - "net" - "time" - - "github.com/square/go-jose" -) - -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` -) - -// Directory ... -type Directory struct { - NewReg string `json:"new-reg"` - RecoverReg string `json:"recover-reg"` - NewAuthz string `json:"new-authz"` - NewCert string `json:"new-cert"` - RevokeCert string `json:"revoke-cert"` -} - -// Registration Objects -type Registration struct { - Resource Resource `json:"resource"` // new-reg - Contact Contacts `json:"contact,omitempty"` - Agreement string `json:"agreement,omitempty"` - Authorizations string `json:"authorizations,omitempty"` - Certificates string `json:"certificates,omitempty"` - ID int `json:"id,omitempty"` - Key *jose.JsonWebKey `json:"key,omitempty"` - InitialIP *net.IP `json:"initialIp,omitempty"` // not in draft - CreatedAt *time.Time `json:"createdAt,omitempty"` -} - -// Authorization request -type Authorization struct { - Resource Resource `json:"resource"` // new-authz - Identifier Identifier `json:"identifier"` - Status Status `json:"status,omitempty"` // e.g. valid - Expires *time.Time `json:"expires,omitempty"` - Challenges []Challenge `json:"challenges,omitempty"` - Combinations [][]int `json:"combinations,omitempty"` -} - -func (a Authorization) Supported(sol map[ChalType]Solver) []Challenge { - supported := func(com []int) bool { - for _, n := range com { - if _, ok := sol[a.Challenges[n].Type]; !ok { - return false - } - } - return true - } - for _, com := range a.Combinations { - if supported(com) { - c := make([]Challenge, len(com)) - for i, n := range com { - c[i] = a.Challenges[n] - } - return c - } - } - return nil -} - -// Identifier ... -type Identifier struct { - Type IdentType `json:"type"` // dns - Value string `json:"value"` // example.com -} - -// Challege ... -type Challenge struct { - Resource Resource `json:"resource"` // challenge - Type ChalType `json:"type"` - Token string `json:"token,omitempty"` - Status Status `json:"status,omitempty"` // e.g. valid - URI string `json:"uri,omitempty"` - Validated *time.Time `json:"validated,omitempty"` - KeyAuthorization string `json:"keyAuthorization,omitempty"` - Err *Problem `json:"error,omitempty"` -} - -// Problem description -type Problem struct { - Type string `json:"type"` - Detail string `json:"detail"` - Instance string `json:"instance"` - Err error `json:"-"` -} - -func (p Problem) Error() string { - return p.Detail -} - -// Status of request -type Status string - -// Statuses -const ( - StatusUnknown Status = "unknown" - StatusPending Status = "pending" - StatusProcessing Status = "processing" - StatusValid Status = "valid" - StatusInvalid Status = "invalid" - StatusRevoked Status = "revoked" -) - -type Resource string - -const ( - ResNewReg Resource = "new-reg" - ResRecoverReg Resource = "recover-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" -) - -type IdentType string - -const IdentDNS IdentType = "dns" - -type ChalType string - -const ( - ChallengeHTTP ChalType = "http-01" - ChallengeTLS ChalType = "tls-sni-01" - ChallengePOP ChalType = "proofOfPossession-01" - ChallengeDNS ChalType = "dns-01" -) - -type CSR struct { - Resource Resource `json:"resource"` // new-cert - CSR string `json:"csr"` -} diff --git a/provider.go b/provider.go index 7cda357..69d125a 100644 --- a/provider.go +++ b/provider.go @@ -4,12 +4,49 @@ import ( "crypto/x509" "encoding/json" "errors" - "log" "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" + ResRecoverReg Resource = "recover-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"` + RecoverReg string `json:"recover-reg"` + NewAuthz string `json:"new-authz"` + NewCert string `json:"new-cert"` + RevokeCert string `json:"revoke-cert"` +} + +// Problem description +type Problem struct { + Type string `json:"type"` + Detail string `json:"detail"` + Instance string `json:"instance"` + Err error `json:"-"` +} + // Provider ... type Provider struct { Directory @@ -34,6 +71,10 @@ const ( poll = time.Second ) +func (p Problem) Error() string { + return p.Detail +} + // RoundTrip implements RoundTipper func (p Provider) RoundTrip(req *http.Request) (*http.Response, error) { resp, err := p.Transport.RoundTrip(req) @@ -130,148 +171,3 @@ func parseCert(resp *http.Response) (*x509.Certificate, error) { return nil, errContentType } } - -func (p *Provider) Register(s Signer, c Contacts) error { - // first step: new-reg - req := &Registration{ - Resource: ResNewReg, - Contact: c, - } - resp, err := p.post(p.NewReg, s, req) - if err != nil { - return err - } - resp.Body.Close() - ns := parseHeader(resp) - - // second step: reg, agree to tos - req = &Registration{ - Resource: ResReg, - Agreement: ns.Link["terms-of-service"], - } - resp, err = p.post(ns.Location, s, req) - if err != nil { - return err - } - resp.Body.Close() - return nil -} - -func (p *Provider) solve(s Signer, ch Challenge, sol Solver) error { - ka, err := s.KeyAuth(ch.Token) - if err != nil { - return err - } - - // prepare solver - err = sol.Solve(ch.Token, ka) - if err != nil { - return err - } - defer sol.Solved() - - // update challenge - ch.Resource = ResChallenge - ch.KeyAuthorization = ka - - resp, err := p.post(ch.URI, s, ch) - if err != nil { - return err - } - ns := parseHeader(resp) - - return p.pollStatus(ns.Location) -} - -func (p *Provider) authz(s Signer, domain string, sol map[ChalType]Solver) error { - // first step: pocke - req := &Authorization{ - Resource: ResNewAuthz, - Identifier: Identifier{ - Type: IdentDNS, - Value: domain, - }, - } - resp, err := p.post(p.NewAuthz, s, req) - if err != nil { - return err - } - err = parseJson(resp, req) - if err != nil { - return err - } - // second step: choose and start solver - for _, ch := range req.Supported(sol) { - if err = p.solve(s, ch, sol[ch.Type]); err != nil { - return err - } - } - return nil -} - -func (p *Provider) Authorize(s Signer, d *Desire) error { - for _, domain := range d.altnames { - if err := p.authz(s, domain, d.solver); err != nil { - return err - } - } - return nil -} - -func (p *Provider) pollStatus(uri string) error { - t := time.NewTicker(poll) - defer t.Stop() - for range t.C { - resp, err := p.Get(uri) - if err != nil { - return err - } - req := new(Challenge) - err = parseJson(resp, req) - if err != nil { - return err - } - if req.Err != nil { - return req.Err - } - log.Println("status", req.Status) - if req.Status == StatusValid { - return nil - } - } - return nil -} - -func (p *Provider) Bundle(s Signer, d *Desire) error { - d.cert = make([]*x509.Certificate, 2) - // first step: post csr - csr, err := d.CSR() - if err != nil { - return err - } - req := &CSR{ - Resource: ResNewCert, - CSR: csr, - } - resp, err := p.post(p.NewCert, s, req) - if err != nil { - return err - } - d.cert[0], err = parseCert(resp) - if err != nil { - return err - } - ns := parseHeader(resp) - - // second step: cet CA - resp, err = p.Get(ns.Link["up"]) - if err != nil { - return err - } - d.cert[1], err = parseCert(resp) - if err != nil { - return err - } - - return nil -} diff --git a/register.go b/register.go new file mode 100644 index 0000000..e002853 --- /dev/null +++ b/register.go @@ -0,0 +1,48 @@ +package acme + +import ( + "net" + "time" + + "github.com/square/go-jose" +) + +// Registration Objects +type Registration struct { + Resource Resource `json:"resource"` // new-reg + Contact Contacts `json:"contact,omitempty"` + Agreement string `json:"agreement,omitempty"` + Authorizations string `json:"authorizations,omitempty"` + Certificates string `json:"certificates,omitempty"` + ID int `json:"id,omitempty"` + Key *jose.JsonWebKey `json:"key,omitempty"` + InitialIP *net.IP `json:"initialIp,omitempty"` // not in draft + CreatedAt *time.Time `json:"createdAt,omitempty"` +} + +func (p *Provider) Register(s Signer, c Contacts) error { + // first step: new-reg + req := &Registration{ + Resource: ResNewReg, + Contact: c, + } + resp, err := p.post(p.NewReg, s, req) + if err != nil { + return err + } + resp.Body.Close() + ns := parseHeader(resp) + + // second step: reg, agree to tos + req = &Registration{ + Resource: ResReg, + Agreement: ns.Link["terms-of-service"], + } + resp, err = p.post(ns.Location, s, req) + if err != nil { + return err + } + resp.Body.Close() + + return nil +} -- cgit v1.2.3