initial commit
This commit is contained in:
512
vendor/golang.org/x/crypto/acme/internal/acme/acme.go
generated
vendored
Normal file
512
vendor/golang.org/x/crypto/acme/internal/acme/acme.go
generated
vendored
Normal file
@@ -0,0 +1,512 @@
|
||||
// Copyright 2015 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package acme provides an ACME client implementation.
|
||||
// See https://ietf-wg-acme.github.io/acme/ for details.
|
||||
//
|
||||
// This package is a work in progress and makes no API stability promises.
|
||||
package acme
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/rsa"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
// LetsEncryptURL is the Directory endpoint of Let's Encrypt CA.
|
||||
const LetsEncryptURL = "https://acme-v01.api.letsencrypt.org/directory"
|
||||
|
||||
// Client is an ACME client.
|
||||
// The only required field is Key. An example of creating a client with a new key
|
||||
// is as follows:
|
||||
//
|
||||
// key, err := rsa.GenerateKey(rand.Reader, 2048)
|
||||
// if err != nil {
|
||||
// log.Fatal(err)
|
||||
// }
|
||||
// client := &Client{Key: key}
|
||||
//
|
||||
type Client struct {
|
||||
// HTTPClient optionally specifies an HTTP client to use
|
||||
// instead of http.DefaultClient.
|
||||
HTTPClient *http.Client
|
||||
|
||||
// Key is the account key used to register with a CA and sign requests.
|
||||
Key *rsa.PrivateKey
|
||||
|
||||
// DirectoryURL points to the CA directory endpoint.
|
||||
// If empty, LetsEncryptURL is used.
|
||||
// Mutating this value after a successful call of Client's Discover method
|
||||
// will have no effect.
|
||||
DirectoryURL string
|
||||
|
||||
dirMu sync.Mutex // guards writes to dir
|
||||
dir *Directory // cached result of Client's Discover method
|
||||
}
|
||||
|
||||
// Discover performs ACME server discovery using c.DirectoryURL.
|
||||
//
|
||||
// It caches successful result. So, subsequent calls will not result in
|
||||
// a network round-trip. This also means mutating c.DirectoryURL after successful call
|
||||
// of this method will have no effect.
|
||||
func (c *Client) Discover() (Directory, error) {
|
||||
c.dirMu.Lock()
|
||||
defer c.dirMu.Unlock()
|
||||
if c.dir != nil {
|
||||
return *c.dir, nil
|
||||
}
|
||||
|
||||
dirURL := c.DirectoryURL
|
||||
if dirURL == "" {
|
||||
dirURL = LetsEncryptURL
|
||||
}
|
||||
res, err := c.httpClient().Get(dirURL)
|
||||
if err != nil {
|
||||
return Directory{}, err
|
||||
}
|
||||
defer res.Body.Close()
|
||||
if res.StatusCode != http.StatusOK {
|
||||
return Directory{}, responseError(res)
|
||||
}
|
||||
|
||||
var v struct {
|
||||
Reg string `json:"new-reg"`
|
||||
Authz string `json:"new-authz"`
|
||||
Cert string `json:"new-cert"`
|
||||
Revoke string `json:"revoke-cert"`
|
||||
Meta struct {
|
||||
Terms string `json:"terms-of-service"`
|
||||
Website string `json:"website"`
|
||||
CAA []string `json:"caa-identities"`
|
||||
}
|
||||
}
|
||||
if json.NewDecoder(res.Body).Decode(&v); err != nil {
|
||||
return Directory{}, err
|
||||
}
|
||||
c.dir = &Directory{
|
||||
RegURL: v.Reg,
|
||||
AuthzURL: v.Authz,
|
||||
CertURL: v.Cert,
|
||||
RevokeURL: v.Revoke,
|
||||
Terms: v.Meta.Terms,
|
||||
Website: v.Meta.Website,
|
||||
CAA: v.Meta.CAA,
|
||||
}
|
||||
return *c.dir, nil
|
||||
}
|
||||
|
||||
// CreateCert requests a new certificate.
|
||||
// In the case where CA server does not provide the issued certificate in the response,
|
||||
// CreateCert will poll certURL using c.FetchCert, which will result in additional round-trips.
|
||||
// In such scenario the caller can cancel the polling with ctx.
|
||||
//
|
||||
// If the bundle is true, the returned value will also contain CA (the issuer) certificate.
|
||||
// The csr is a DER encoded certificate signing request.
|
||||
func (c *Client) CreateCert(ctx context.Context, csr []byte, exp time.Duration, bundle bool) (der [][]byte, certURL string, err error) {
|
||||
if _, err := c.Discover(); err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
req := struct {
|
||||
Resource string `json:"resource"`
|
||||
CSR string `json:"csr"`
|
||||
NotBefore string `json:"notBefore,omitempty"`
|
||||
NotAfter string `json:"notAfter,omitempty"`
|
||||
}{
|
||||
Resource: "new-cert",
|
||||
CSR: base64.RawURLEncoding.EncodeToString(csr),
|
||||
}
|
||||
now := timeNow()
|
||||
req.NotBefore = now.Format(time.RFC3339)
|
||||
if exp > 0 {
|
||||
req.NotAfter = now.Add(exp).Format(time.RFC3339)
|
||||
}
|
||||
|
||||
res, err := c.postJWS(c.dir.CertURL, req)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
defer res.Body.Close()
|
||||
if res.StatusCode != http.StatusCreated {
|
||||
return nil, "", responseError(res)
|
||||
}
|
||||
|
||||
curl := res.Header.Get("location") // cert permanent URL
|
||||
if res.ContentLength == 0 {
|
||||
// no cert in the body; poll until we get it
|
||||
cert, err := c.FetchCert(ctx, curl, bundle)
|
||||
return cert, curl, err
|
||||
}
|
||||
// slurp issued cert and ca, if requested
|
||||
cert, err := responseCert(c.httpClient(), res, bundle)
|
||||
return cert, curl, err
|
||||
}
|
||||
|
||||
// FetchCert retrieves already issued certificate from the given url, in DER format.
|
||||
// It retries the request until the certificate is successfully retrieved,
|
||||
// context is cancelled by the caller or an error response is received.
|
||||
//
|
||||
// The returned value will also contain CA (the issuer) certificate if bundle is true.
|
||||
func (c *Client) FetchCert(ctx context.Context, url string, bundle bool) ([][]byte, error) {
|
||||
for {
|
||||
res, err := c.httpClient().Get(url)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer res.Body.Close()
|
||||
if res.StatusCode == http.StatusOK {
|
||||
return responseCert(c.httpClient(), res, bundle)
|
||||
}
|
||||
if res.StatusCode > 299 {
|
||||
return nil, responseError(res)
|
||||
}
|
||||
d, err := retryAfter(res.Header.Get("retry-after"))
|
||||
if err != nil {
|
||||
d = 3 * time.Second
|
||||
}
|
||||
select {
|
||||
case <-time.After(d):
|
||||
// retry
|
||||
case <-ctx.Done():
|
||||
return nil, ctx.Err()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Register creates a new account registration by following the "new-reg" flow.
|
||||
// It returns registered account. The a argument is not modified.
|
||||
func (c *Client) Register(a *Account) (*Account, error) {
|
||||
if _, err := c.Discover(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return c.doReg(c.dir.RegURL, "new-reg", a)
|
||||
}
|
||||
|
||||
// GetReg retrieves an existing registration.
|
||||
// The url argument is an Account URI.
|
||||
func (c *Client) GetReg(url string) (*Account, error) {
|
||||
a := &Account{URI: url}
|
||||
return c.doReg(url, "reg", a)
|
||||
}
|
||||
|
||||
// UpdateReg updates an existing registration.
|
||||
// It returns an updated account copy. The provided account is not modified.
|
||||
func (c *Client) UpdateReg(a *Account) (*Account, error) {
|
||||
return c.doReg(a.URI, "reg", a)
|
||||
}
|
||||
|
||||
// Authorize performs the initial step in an authorization flow.
|
||||
// The caller will then need to choose from and perform a set of returned
|
||||
// challenges using c.Accept in order to successfully complete authorization.
|
||||
func (c *Client) Authorize(domain string) (*Authorization, error) {
|
||||
if _, err := c.Discover(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
type authzID struct {
|
||||
Type string `json:"type"`
|
||||
Value string `json:"value"`
|
||||
}
|
||||
req := struct {
|
||||
Resource string `json:"resource"`
|
||||
Identifier authzID `json:"identifier"`
|
||||
}{
|
||||
Resource: "new-authz",
|
||||
Identifier: authzID{Type: "dns", Value: domain},
|
||||
}
|
||||
res, err := c.postJWS(c.dir.AuthzURL, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer res.Body.Close()
|
||||
if res.StatusCode != http.StatusCreated {
|
||||
return nil, responseError(res)
|
||||
}
|
||||
|
||||
var v wireAuthz
|
||||
if err := json.NewDecoder(res.Body).Decode(&v); err != nil {
|
||||
return nil, fmt.Errorf("Decode: %v", err)
|
||||
}
|
||||
if v.Status != StatusPending {
|
||||
return nil, fmt.Errorf("Unexpected status: %s", v.Status)
|
||||
}
|
||||
return v.authorization(res.Header.Get("Location")), nil
|
||||
}
|
||||
|
||||
// GetAuthz retrieves the current status of an authorization flow.
|
||||
//
|
||||
// A client typically polls an authz status using this method.
|
||||
func (c *Client) GetAuthz(url string) (*Authorization, error) {
|
||||
res, err := c.httpClient().Get(url)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer res.Body.Close()
|
||||
if res.StatusCode != http.StatusOK && res.StatusCode != http.StatusAccepted {
|
||||
return nil, responseError(res)
|
||||
}
|
||||
var v wireAuthz
|
||||
if err := json.NewDecoder(res.Body).Decode(&v); err != nil {
|
||||
return nil, fmt.Errorf("Decode: %v", err)
|
||||
}
|
||||
return v.authorization(url), nil
|
||||
}
|
||||
|
||||
// GetChallenge retrieves the current status of an challenge.
|
||||
//
|
||||
// A client typically polls a challenge status using this method.
|
||||
func (c *Client) GetChallenge(url string) (*Challenge, error) {
|
||||
res, err := c.httpClient().Get(url)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer res.Body.Close()
|
||||
if res.StatusCode != http.StatusOK && res.StatusCode != http.StatusAccepted {
|
||||
return nil, responseError(res)
|
||||
}
|
||||
v := wireChallenge{URI: url}
|
||||
if err := json.NewDecoder(res.Body).Decode(&v); err != nil {
|
||||
return nil, fmt.Errorf("Decode: %v", err)
|
||||
}
|
||||
return v.challenge(), nil
|
||||
}
|
||||
|
||||
// Accept informs the server that the client accepts one of its challenges
|
||||
// previously obtained with c.Authorize.
|
||||
//
|
||||
// The server will then perform the validation asynchronously.
|
||||
func (c *Client) Accept(chal *Challenge) (*Challenge, error) {
|
||||
req := struct {
|
||||
Resource string `json:"resource"`
|
||||
Type string `json:"type"`
|
||||
Auth string `json:"keyAuthorization"`
|
||||
}{
|
||||
Resource: "challenge",
|
||||
Type: chal.Type,
|
||||
Auth: keyAuth(&c.Key.PublicKey, chal.Token),
|
||||
}
|
||||
res, err := c.postJWS(chal.URI, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer res.Body.Close()
|
||||
// Note: the protocol specifies 200 as the expected response code, but
|
||||
// letsencrypt seems to be returning 202.
|
||||
if res.StatusCode != http.StatusOK && res.StatusCode != http.StatusAccepted {
|
||||
return nil, responseError(res)
|
||||
}
|
||||
|
||||
var v wireChallenge
|
||||
if err := json.NewDecoder(res.Body).Decode(&v); err != nil {
|
||||
return nil, fmt.Errorf("Decode: %v", err)
|
||||
}
|
||||
return v.challenge(), nil
|
||||
}
|
||||
|
||||
// HTTP01Handler creates a new handler which responds to a http-01 challenge.
|
||||
// The token argument is a Challenge.Token value.
|
||||
func (c *Client) HTTP01Handler(token string) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if !strings.HasSuffix(r.URL.Path, token) {
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
w.Header().Set("content-type", "text/plain")
|
||||
w.Write([]byte(keyAuth(&c.Key.PublicKey, token)))
|
||||
})
|
||||
}
|
||||
|
||||
func (c *Client) httpClient() *http.Client {
|
||||
if c.HTTPClient != nil {
|
||||
return c.HTTPClient
|
||||
}
|
||||
return http.DefaultClient
|
||||
}
|
||||
|
||||
// postJWS signs body and posts it to the provided url.
|
||||
// The body argument must be JSON-serializable.
|
||||
func (c *Client) postJWS(url string, body interface{}) (*http.Response, error) {
|
||||
nonce, err := fetchNonce(c.httpClient(), url)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
b, err := jwsEncodeJSON(body, c.Key, nonce)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req, err := http.NewRequest("POST", url, bytes.NewReader(b))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return c.httpClient().Do(req)
|
||||
}
|
||||
|
||||
// doReg sends all types of registration requests.
|
||||
// The type of request is identified by typ argument, which is a "resource"
|
||||
// in the ACME spec terms.
|
||||
//
|
||||
// A non-nil acct argument indicates whether the intention is to mutate data
|
||||
// of the Account. Only Contact and Agreement of its fields are used
|
||||
// in such cases.
|
||||
//
|
||||
// The fields of acct will be populate with the server response
|
||||
// and may be overwritten.
|
||||
func (c *Client) doReg(url string, typ string, acct *Account) (*Account, error) {
|
||||
req := struct {
|
||||
Resource string `json:"resource"`
|
||||
Contact []string `json:"contact,omitempty"`
|
||||
Agreement string `json:"agreement,omitempty"`
|
||||
}{
|
||||
Resource: typ,
|
||||
}
|
||||
if acct != nil {
|
||||
req.Contact = acct.Contact
|
||||
req.Agreement = acct.AgreedTerms
|
||||
}
|
||||
res, err := c.postJWS(url, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer res.Body.Close()
|
||||
if res.StatusCode < 200 || res.StatusCode > 299 {
|
||||
return nil, responseError(res)
|
||||
}
|
||||
|
||||
var v struct {
|
||||
Contact []string
|
||||
Agreement string
|
||||
Authorizations string
|
||||
Certificates string
|
||||
}
|
||||
if err := json.NewDecoder(res.Body).Decode(&v); err != nil {
|
||||
return nil, fmt.Errorf("Decode: %v", err)
|
||||
}
|
||||
return &Account{
|
||||
URI: res.Header.Get("Location"),
|
||||
Contact: v.Contact,
|
||||
AgreedTerms: v.Agreement,
|
||||
CurrentTerms: linkHeader(res.Header, "terms-of-service"),
|
||||
Authz: linkHeader(res.Header, "next"),
|
||||
Authorizations: v.Authorizations,
|
||||
Certificates: v.Certificates,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func responseCert(client *http.Client, res *http.Response, bundle bool) ([][]byte, error) {
|
||||
b, err := ioutil.ReadAll(res.Body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("ReadAll: %v", err)
|
||||
}
|
||||
cert := [][]byte{b}
|
||||
if !bundle {
|
||||
return cert, nil
|
||||
}
|
||||
|
||||
// append ca cert
|
||||
up := linkHeader(res.Header, "up")
|
||||
if up == "" {
|
||||
return nil, errors.New("rel=up link not found")
|
||||
}
|
||||
res, err = client.Get(up)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer res.Body.Close()
|
||||
if res.StatusCode != http.StatusOK {
|
||||
return nil, responseError(res)
|
||||
}
|
||||
b, err = ioutil.ReadAll(res.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return append(cert, b), nil
|
||||
}
|
||||
|
||||
// responseError creates an error of Error type from resp.
|
||||
func responseError(resp *http.Response) error {
|
||||
// don't care if ReadAll returns an error:
|
||||
// json.Unmarshal will fail in that case anyway
|
||||
b, _ := ioutil.ReadAll(resp.Body)
|
||||
e := struct {
|
||||
Status int
|
||||
Type string
|
||||
Detail string
|
||||
}{
|
||||
Status: resp.StatusCode,
|
||||
}
|
||||
if err := json.Unmarshal(b, &e); err != nil {
|
||||
// this is not a regular error response:
|
||||
// populate detail with anything we received,
|
||||
// e.Status will already contain HTTP response code value
|
||||
e.Detail = string(b)
|
||||
if e.Detail == "" {
|
||||
e.Detail = resp.Status
|
||||
}
|
||||
}
|
||||
return &Error{
|
||||
StatusCode: e.Status,
|
||||
ProblemType: e.Type,
|
||||
Detail: e.Detail,
|
||||
Header: resp.Header,
|
||||
}
|
||||
}
|
||||
|
||||
func fetchNonce(client *http.Client, url string) (string, error) {
|
||||
resp, err := client.Head(url)
|
||||
if err != nil {
|
||||
return "", nil
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
enc := resp.Header.Get("replay-nonce")
|
||||
if enc == "" {
|
||||
return "", errors.New("nonce not found")
|
||||
}
|
||||
return enc, nil
|
||||
}
|
||||
|
||||
func linkHeader(h http.Header, rel string) string {
|
||||
for _, v := range h["Link"] {
|
||||
parts := strings.Split(v, ";")
|
||||
for _, p := range parts {
|
||||
p = strings.TrimSpace(p)
|
||||
if !strings.HasPrefix(p, "rel=") {
|
||||
continue
|
||||
}
|
||||
if v := strings.Trim(p[4:], `"`); v == rel {
|
||||
return strings.Trim(parts[0], "<>")
|
||||
}
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func retryAfter(v string) (time.Duration, error) {
|
||||
if i, err := strconv.Atoi(v); err == nil {
|
||||
return time.Duration(i) * time.Second, nil
|
||||
}
|
||||
t, err := http.ParseTime(v)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return t.Sub(timeNow()), nil
|
||||
}
|
||||
|
||||
// keyAuth generates a key authorization string for a given token.
|
||||
func keyAuth(pub *rsa.PublicKey, token string) string {
|
||||
return fmt.Sprintf("%s.%s", token, JWKThumbprint(pub))
|
||||
}
|
||||
|
||||
// timeNow is useful for testing for fixed current time.
|
||||
var timeNow = time.Now
|
||||
760
vendor/golang.org/x/crypto/acme/internal/acme/acme_test.go
generated
vendored
Normal file
760
vendor/golang.org/x/crypto/acme/internal/acme/acme_test.go
generated
vendored
Normal file
@@ -0,0 +1,760 @@
|
||||
// Copyright 2015 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package acme
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"math/big"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
// Decodes a JWS-encoded request and unmarshals the decoded JSON into a provided
|
||||
// interface.
|
||||
func decodeJWSRequest(t *testing.T, v interface{}, r *http.Request) {
|
||||
// Decode request
|
||||
var req struct{ Payload string }
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
payload, err := base64.RawURLEncoding.DecodeString(req.Payload)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = json.Unmarshal(payload, v)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDiscover(t *testing.T) {
|
||||
const (
|
||||
reg = "https://example.com/acme/new-reg"
|
||||
authz = "https://example.com/acme/new-authz"
|
||||
cert = "https://example.com/acme/new-cert"
|
||||
revoke = "https://example.com/acme/revoke-cert"
|
||||
)
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("content-type", "application/json")
|
||||
fmt.Fprintf(w, `{
|
||||
"new-reg": %q,
|
||||
"new-authz": %q,
|
||||
"new-cert": %q,
|
||||
"revoke-cert": %q
|
||||
}`, reg, authz, cert, revoke)
|
||||
}))
|
||||
defer ts.Close()
|
||||
c := Client{DirectoryURL: ts.URL}
|
||||
dir, err := c.Discover()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if dir.RegURL != reg {
|
||||
t.Errorf("dir.RegURL = %q; want %q", dir.RegURL, reg)
|
||||
}
|
||||
if dir.AuthzURL != authz {
|
||||
t.Errorf("dir.AuthzURL = %q; want %q", dir.AuthzURL, authz)
|
||||
}
|
||||
if dir.CertURL != cert {
|
||||
t.Errorf("dir.CertURL = %q; want %q", dir.CertURL, cert)
|
||||
}
|
||||
if dir.RevokeURL != revoke {
|
||||
t.Errorf("dir.RevokeURL = %q; want %q", dir.RevokeURL, revoke)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRegister(t *testing.T) {
|
||||
contacts := []string{"mailto:admin@example.com"}
|
||||
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method == "HEAD" {
|
||||
w.Header().Set("replay-nonce", "test-nonce")
|
||||
return
|
||||
}
|
||||
if r.Method != "POST" {
|
||||
t.Errorf("r.Method = %q; want POST", r.Method)
|
||||
}
|
||||
|
||||
var j struct {
|
||||
Resource string
|
||||
Contact []string
|
||||
Agreement string
|
||||
}
|
||||
decodeJWSRequest(t, &j, r)
|
||||
|
||||
// Test request
|
||||
if j.Resource != "new-reg" {
|
||||
t.Errorf("j.Resource = %q; want new-reg", j.Resource)
|
||||
}
|
||||
if !reflect.DeepEqual(j.Contact, contacts) {
|
||||
t.Errorf("j.Contact = %v; want %v", j.Contact, contacts)
|
||||
}
|
||||
|
||||
w.Header().Set("Location", "https://ca.tld/acme/reg/1")
|
||||
w.Header().Set("Link", `<https://ca.tld/acme/new-authz>;rel="next"`)
|
||||
w.Header().Add("Link", `<https://ca.tld/acme/recover-reg>;rel="recover"`)
|
||||
w.Header().Add("Link", `<https://ca.tld/acme/terms>;rel="terms-of-service"`)
|
||||
w.WriteHeader(http.StatusCreated)
|
||||
b, _ := json.Marshal(contacts)
|
||||
fmt.Fprintf(w, `{
|
||||
"key":%q,
|
||||
"contact":%s
|
||||
}`, testKeyThumbprint, b)
|
||||
}))
|
||||
defer ts.Close()
|
||||
|
||||
c := Client{Key: testKey, dir: &Directory{RegURL: ts.URL}}
|
||||
a := &Account{Contact: contacts}
|
||||
var err error
|
||||
if a, err = c.Register(a); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if a.URI != "https://ca.tld/acme/reg/1" {
|
||||
t.Errorf("a.URI = %q; want https://ca.tld/acme/reg/1", a.URI)
|
||||
}
|
||||
if a.Authz != "https://ca.tld/acme/new-authz" {
|
||||
t.Errorf("a.Authz = %q; want https://ca.tld/acme/new-authz", a.Authz)
|
||||
}
|
||||
if a.CurrentTerms != "https://ca.tld/acme/terms" {
|
||||
t.Errorf("a.CurrentTerms = %q; want https://ca.tld/acme/terms", a.CurrentTerms)
|
||||
}
|
||||
if !reflect.DeepEqual(a.Contact, contacts) {
|
||||
t.Errorf("a.Contact = %v; want %v", a.Contact, contacts)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateReg(t *testing.T) {
|
||||
const terms = "https://ca.tld/acme/terms"
|
||||
contacts := []string{"mailto:admin@example.com"}
|
||||
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method == "HEAD" {
|
||||
w.Header().Set("replay-nonce", "test-nonce")
|
||||
return
|
||||
}
|
||||
if r.Method != "POST" {
|
||||
t.Errorf("r.Method = %q; want POST", r.Method)
|
||||
}
|
||||
|
||||
var j struct {
|
||||
Resource string
|
||||
Contact []string
|
||||
Agreement string
|
||||
}
|
||||
decodeJWSRequest(t, &j, r)
|
||||
|
||||
// Test request
|
||||
if j.Resource != "reg" {
|
||||
t.Errorf("j.Resource = %q; want reg", j.Resource)
|
||||
}
|
||||
if j.Agreement != terms {
|
||||
t.Errorf("j.Agreement = %q; want %q", j.Agreement, terms)
|
||||
}
|
||||
if !reflect.DeepEqual(j.Contact, contacts) {
|
||||
t.Errorf("j.Contact = %v; want %v", j.Contact, contacts)
|
||||
}
|
||||
|
||||
w.Header().Set("Link", `<https://ca.tld/acme/new-authz>;rel="next"`)
|
||||
w.Header().Add("Link", `<https://ca.tld/acme/recover-reg>;rel="recover"`)
|
||||
w.Header().Add("Link", fmt.Sprintf(`<%s>;rel="terms-of-service"`, terms))
|
||||
w.WriteHeader(http.StatusOK)
|
||||
b, _ := json.Marshal(contacts)
|
||||
fmt.Fprintf(w, `{
|
||||
"key":%q,
|
||||
"contact":%s,
|
||||
"agreement":%q
|
||||
}`, testKeyThumbprint, b, terms)
|
||||
}))
|
||||
defer ts.Close()
|
||||
|
||||
c := Client{Key: testKey}
|
||||
a := &Account{URI: ts.URL, Contact: contacts, AgreedTerms: terms}
|
||||
var err error
|
||||
if a, err = c.UpdateReg(a); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if a.Authz != "https://ca.tld/acme/new-authz" {
|
||||
t.Errorf("a.Authz = %q; want https://ca.tld/acme/new-authz", a.Authz)
|
||||
}
|
||||
if a.AgreedTerms != terms {
|
||||
t.Errorf("a.AgreedTerms = %q; want %q", a.AgreedTerms, terms)
|
||||
}
|
||||
if a.CurrentTerms != terms {
|
||||
t.Errorf("a.CurrentTerms = %q; want %q", a.CurrentTerms, terms)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetReg(t *testing.T) {
|
||||
const terms = "https://ca.tld/acme/terms"
|
||||
const newTerms = "https://ca.tld/acme/new-terms"
|
||||
contacts := []string{"mailto:admin@example.com"}
|
||||
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method == "HEAD" {
|
||||
w.Header().Set("replay-nonce", "test-nonce")
|
||||
return
|
||||
}
|
||||
if r.Method != "POST" {
|
||||
t.Errorf("r.Method = %q; want POST", r.Method)
|
||||
}
|
||||
|
||||
var j struct {
|
||||
Resource string
|
||||
Contact []string
|
||||
Agreement string
|
||||
}
|
||||
decodeJWSRequest(t, &j, r)
|
||||
|
||||
// Test request
|
||||
if j.Resource != "reg" {
|
||||
t.Errorf("j.Resource = %q; want reg", j.Resource)
|
||||
}
|
||||
if len(j.Contact) != 0 {
|
||||
t.Errorf("j.Contact = %v", j.Contact)
|
||||
}
|
||||
if j.Agreement != "" {
|
||||
t.Errorf("j.Agreement = %q", j.Agreement)
|
||||
}
|
||||
|
||||
w.Header().Set("Link", `<https://ca.tld/acme/new-authz>;rel="next"`)
|
||||
w.Header().Add("Link", `<https://ca.tld/acme/recover-reg>;rel="recover"`)
|
||||
w.Header().Add("Link", fmt.Sprintf(`<%s>;rel="terms-of-service"`, newTerms))
|
||||
w.WriteHeader(http.StatusOK)
|
||||
b, _ := json.Marshal(contacts)
|
||||
fmt.Fprintf(w, `{
|
||||
"key":%q,
|
||||
"contact":%s,
|
||||
"agreement":%q
|
||||
}`, testKeyThumbprint, b, terms)
|
||||
}))
|
||||
defer ts.Close()
|
||||
|
||||
c := Client{Key: testKey}
|
||||
a, err := c.GetReg(ts.URL)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if a.Authz != "https://ca.tld/acme/new-authz" {
|
||||
t.Errorf("a.AuthzURL = %q; want https://ca.tld/acme/new-authz", a.Authz)
|
||||
}
|
||||
if a.AgreedTerms != terms {
|
||||
t.Errorf("a.AgreedTerms = %q; want %q", a.AgreedTerms, terms)
|
||||
}
|
||||
if a.CurrentTerms != newTerms {
|
||||
t.Errorf("a.CurrentTerms = %q; want %q", a.CurrentTerms, newTerms)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAuthorize(t *testing.T) {
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method == "HEAD" {
|
||||
w.Header().Set("replay-nonce", "test-nonce")
|
||||
return
|
||||
}
|
||||
if r.Method != "POST" {
|
||||
t.Errorf("r.Method = %q; want POST", r.Method)
|
||||
}
|
||||
|
||||
var j struct {
|
||||
Resource string
|
||||
Identifier struct {
|
||||
Type string
|
||||
Value string
|
||||
}
|
||||
}
|
||||
decodeJWSRequest(t, &j, r)
|
||||
|
||||
// Test request
|
||||
if j.Resource != "new-authz" {
|
||||
t.Errorf("j.Resource = %q; want new-authz", j.Resource)
|
||||
}
|
||||
if j.Identifier.Type != "dns" {
|
||||
t.Errorf("j.Identifier.Type = %q; want dns", j.Identifier.Type)
|
||||
}
|
||||
if j.Identifier.Value != "example.com" {
|
||||
t.Errorf("j.Identifier.Value = %q; want example.com", j.Identifier.Value)
|
||||
}
|
||||
|
||||
w.Header().Set("Location", "https://ca.tld/acme/auth/1")
|
||||
w.WriteHeader(http.StatusCreated)
|
||||
fmt.Fprintf(w, `{
|
||||
"identifier": {"type":"dns","value":"example.com"},
|
||||
"status":"pending",
|
||||
"challenges":[
|
||||
{
|
||||
"type":"http-01",
|
||||
"status":"pending",
|
||||
"uri":"https://ca.tld/acme/challenge/publickey/id1",
|
||||
"token":"token1"
|
||||
},
|
||||
{
|
||||
"type":"tls-sni-01",
|
||||
"status":"pending",
|
||||
"uri":"https://ca.tld/acme/challenge/publickey/id2",
|
||||
"token":"token2"
|
||||
}
|
||||
],
|
||||
"combinations":[[0],[1]]}`)
|
||||
}))
|
||||
defer ts.Close()
|
||||
|
||||
cl := Client{Key: testKey, dir: &Directory{AuthzURL: ts.URL}}
|
||||
auth, err := cl.Authorize("example.com")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if auth.URI != "https://ca.tld/acme/auth/1" {
|
||||
t.Errorf("URI = %q; want https://ca.tld/acme/auth/1", auth.URI)
|
||||
}
|
||||
if auth.Status != "pending" {
|
||||
t.Errorf("Status = %q; want pending", auth.Status)
|
||||
}
|
||||
if auth.Identifier.Type != "dns" {
|
||||
t.Errorf("Identifier.Type = %q; want dns", auth.Identifier.Type)
|
||||
}
|
||||
if auth.Identifier.Value != "example.com" {
|
||||
t.Errorf("Identifier.Value = %q; want example.com", auth.Identifier.Value)
|
||||
}
|
||||
|
||||
if n := len(auth.Challenges); n != 2 {
|
||||
t.Fatalf("len(auth.Challenges) = %d; want 2", n)
|
||||
}
|
||||
|
||||
c := auth.Challenges[0]
|
||||
if c.Type != "http-01" {
|
||||
t.Errorf("c.Type = %q; want http-01", c.Type)
|
||||
}
|
||||
if c.URI != "https://ca.tld/acme/challenge/publickey/id1" {
|
||||
t.Errorf("c.URI = %q; want https://ca.tld/acme/challenge/publickey/id1", c.URI)
|
||||
}
|
||||
if c.Token != "token1" {
|
||||
t.Errorf("c.Token = %q; want token1", c.Type)
|
||||
}
|
||||
|
||||
c = auth.Challenges[1]
|
||||
if c.Type != "tls-sni-01" {
|
||||
t.Errorf("c.Type = %q; want tls-sni-01", c.Type)
|
||||
}
|
||||
if c.URI != "https://ca.tld/acme/challenge/publickey/id2" {
|
||||
t.Errorf("c.URI = %q; want https://ca.tld/acme/challenge/publickey/id2", c.URI)
|
||||
}
|
||||
if c.Token != "token2" {
|
||||
t.Errorf("c.Token = %q; want token2", c.Type)
|
||||
}
|
||||
|
||||
combs := [][]int{{0}, {1}}
|
||||
if !reflect.DeepEqual(auth.Combinations, combs) {
|
||||
t.Errorf("auth.Combinations: %+v\nwant: %+v\n", auth.Combinations, combs)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPollAuthz(t *testing.T) {
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != "GET" {
|
||||
t.Errorf("r.Method = %q; want GET", r.Method)
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusOK)
|
||||
fmt.Fprintf(w, `{
|
||||
"identifier": {"type":"dns","value":"example.com"},
|
||||
"status":"pending",
|
||||
"challenges":[
|
||||
{
|
||||
"type":"http-01",
|
||||
"status":"pending",
|
||||
"uri":"https://ca.tld/acme/challenge/publickey/id1",
|
||||
"token":"token1"
|
||||
},
|
||||
{
|
||||
"type":"tls-sni-01",
|
||||
"status":"pending",
|
||||
"uri":"https://ca.tld/acme/challenge/publickey/id2",
|
||||
"token":"token2"
|
||||
}
|
||||
],
|
||||
"combinations":[[0],[1]]}`)
|
||||
}))
|
||||
defer ts.Close()
|
||||
|
||||
cl := Client{Key: testKey}
|
||||
auth, err := cl.GetAuthz(ts.URL)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if auth.Status != "pending" {
|
||||
t.Errorf("Status = %q; want pending", auth.Status)
|
||||
}
|
||||
if auth.Identifier.Type != "dns" {
|
||||
t.Errorf("Identifier.Type = %q; want dns", auth.Identifier.Type)
|
||||
}
|
||||
if auth.Identifier.Value != "example.com" {
|
||||
t.Errorf("Identifier.Value = %q; want example.com", auth.Identifier.Value)
|
||||
}
|
||||
|
||||
if n := len(auth.Challenges); n != 2 {
|
||||
t.Fatalf("len(set.Challenges) = %d; want 2", n)
|
||||
}
|
||||
|
||||
c := auth.Challenges[0]
|
||||
if c.Type != "http-01" {
|
||||
t.Errorf("c.Type = %q; want http-01", c.Type)
|
||||
}
|
||||
if c.URI != "https://ca.tld/acme/challenge/publickey/id1" {
|
||||
t.Errorf("c.URI = %q; want https://ca.tld/acme/challenge/publickey/id1", c.URI)
|
||||
}
|
||||
if c.Token != "token1" {
|
||||
t.Errorf("c.Token = %q; want token1", c.Type)
|
||||
}
|
||||
|
||||
c = auth.Challenges[1]
|
||||
if c.Type != "tls-sni-01" {
|
||||
t.Errorf("c.Type = %q; want tls-sni-01", c.Type)
|
||||
}
|
||||
if c.URI != "https://ca.tld/acme/challenge/publickey/id2" {
|
||||
t.Errorf("c.URI = %q; want https://ca.tld/acme/challenge/publickey/id2", c.URI)
|
||||
}
|
||||
if c.Token != "token2" {
|
||||
t.Errorf("c.Token = %q; want token2", c.Type)
|
||||
}
|
||||
|
||||
combs := [][]int{{0}, {1}}
|
||||
if !reflect.DeepEqual(auth.Combinations, combs) {
|
||||
t.Errorf("auth.Combinations: %+v\nwant: %+v\n", auth.Combinations, combs)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPollChallenge(t *testing.T) {
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != "GET" {
|
||||
t.Errorf("r.Method = %q; want GET", r.Method)
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusOK)
|
||||
fmt.Fprintf(w, `{
|
||||
"type":"http-01",
|
||||
"status":"pending",
|
||||
"uri":"https://ca.tld/acme/challenge/publickey/id1",
|
||||
"token":"token1"}`)
|
||||
}))
|
||||
defer ts.Close()
|
||||
|
||||
cl := Client{Key: testKey}
|
||||
chall, err := cl.GetChallenge(ts.URL)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if chall.Status != "pending" {
|
||||
t.Errorf("Status = %q; want pending", chall.Status)
|
||||
}
|
||||
if chall.Type != "http-01" {
|
||||
t.Errorf("c.Type = %q; want http-01", chall.Type)
|
||||
}
|
||||
if chall.URI != "https://ca.tld/acme/challenge/publickey/id1" {
|
||||
t.Errorf("c.URI = %q; want https://ca.tld/acme/challenge/publickey/id1", chall.URI)
|
||||
}
|
||||
if chall.Token != "token1" {
|
||||
t.Errorf("c.Token = %q; want token1", chall.Type)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAcceptChallenge(t *testing.T) {
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method == "HEAD" {
|
||||
w.Header().Set("replay-nonce", "test-nonce")
|
||||
return
|
||||
}
|
||||
if r.Method != "POST" {
|
||||
t.Errorf("r.Method = %q; want POST", r.Method)
|
||||
}
|
||||
|
||||
var j struct {
|
||||
Resource string
|
||||
Type string
|
||||
Auth string `json:"keyAuthorization"`
|
||||
}
|
||||
decodeJWSRequest(t, &j, r)
|
||||
|
||||
// Test request
|
||||
if j.Resource != "challenge" {
|
||||
t.Errorf(`resource = %q; want "challenge"`, j.Resource)
|
||||
}
|
||||
if j.Type != "http-01" {
|
||||
t.Errorf(`type = %q; want "http-01"`, j.Type)
|
||||
}
|
||||
keyAuth := "token1." + testKeyThumbprint
|
||||
if j.Auth != keyAuth {
|
||||
t.Errorf(`keyAuthorization = %q; want %q`, j.Auth, keyAuth)
|
||||
}
|
||||
|
||||
// Respond to request
|
||||
w.WriteHeader(http.StatusAccepted)
|
||||
fmt.Fprintf(w, `{
|
||||
"type":"http-01",
|
||||
"status":"pending",
|
||||
"uri":"https://ca.tld/acme/challenge/publickey/id1",
|
||||
"token":"token1",
|
||||
"keyAuthorization":%q
|
||||
}`, keyAuth)
|
||||
}))
|
||||
defer ts.Close()
|
||||
|
||||
cl := Client{Key: testKey}
|
||||
c, err := cl.Accept(&Challenge{
|
||||
URI: ts.URL,
|
||||
Token: "token1",
|
||||
Type: "http-01",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if c.Type != "http-01" {
|
||||
t.Errorf("c.Type = %q; want http-01", c.Type)
|
||||
}
|
||||
if c.URI != "https://ca.tld/acme/challenge/publickey/id1" {
|
||||
t.Errorf("c.URI = %q; want https://ca.tld/acme/challenge/publickey/id1", c.URI)
|
||||
}
|
||||
if c.Token != "token1" {
|
||||
t.Errorf("c.Token = %q; want token1", c.Type)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewCert(t *testing.T) {
|
||||
notBefore := time.Now()
|
||||
notAfter := notBefore.AddDate(0, 2, 0)
|
||||
timeNow = func() time.Time { return notBefore }
|
||||
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method == "HEAD" {
|
||||
w.Header().Set("replay-nonce", "test-nonce")
|
||||
return
|
||||
}
|
||||
if r.Method != "POST" {
|
||||
t.Errorf("r.Method = %q; want POST", r.Method)
|
||||
}
|
||||
|
||||
var j struct {
|
||||
Resource string `json:"resource"`
|
||||
CSR string `json:"csr"`
|
||||
NotBefore string `json:"notBefore,omitempty"`
|
||||
NotAfter string `json:"notAfter,omitempty"`
|
||||
}
|
||||
decodeJWSRequest(t, &j, r)
|
||||
|
||||
// Test request
|
||||
if j.Resource != "new-cert" {
|
||||
t.Errorf(`resource = %q; want "new-cert"`, j.Resource)
|
||||
}
|
||||
if j.NotBefore != notBefore.Format(time.RFC3339) {
|
||||
t.Errorf(`notBefore = %q; wanted %q`, j.NotBefore, notBefore.Format(time.RFC3339))
|
||||
}
|
||||
if j.NotAfter != notAfter.Format(time.RFC3339) {
|
||||
t.Errorf(`notAfter = %q; wanted %q`, j.NotAfter, notAfter.Format(time.RFC3339))
|
||||
}
|
||||
|
||||
// Respond to request
|
||||
template := x509.Certificate{
|
||||
SerialNumber: big.NewInt(int64(1)),
|
||||
Subject: pkix.Name{
|
||||
Organization: []string{"goacme"},
|
||||
},
|
||||
NotBefore: notBefore,
|
||||
NotAfter: notAfter,
|
||||
|
||||
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
|
||||
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
|
||||
BasicConstraintsValid: true,
|
||||
}
|
||||
|
||||
sampleCert, err := x509.CreateCertificate(rand.Reader, &template, &template, &testKey.PublicKey, testKey)
|
||||
if err != nil {
|
||||
t.Fatalf("Error creating certificate: %v", err)
|
||||
}
|
||||
|
||||
w.Header().Set("Location", "https://ca.tld/acme/cert/1")
|
||||
w.WriteHeader(http.StatusCreated)
|
||||
w.Write(sampleCert)
|
||||
}))
|
||||
defer ts.Close()
|
||||
|
||||
csr := x509.CertificateRequest{
|
||||
Version: 0,
|
||||
Subject: pkix.Name{
|
||||
CommonName: "example.com",
|
||||
Organization: []string{"goacme"},
|
||||
},
|
||||
}
|
||||
csrb, err := x509.CreateCertificateRequest(rand.Reader, &csr, testKey)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
c := Client{Key: testKey, dir: &Directory{CertURL: ts.URL}}
|
||||
cert, certURL, err := c.CreateCert(context.Background(), csrb, notAfter.Sub(notBefore), false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if cert == nil {
|
||||
t.Errorf("cert is nil")
|
||||
}
|
||||
if certURL != "https://ca.tld/acme/cert/1" {
|
||||
t.Errorf("certURL = %q; want https://ca.tld/acme/cert/1", certURL)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFetchCert(t *testing.T) {
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write([]byte{1})
|
||||
}))
|
||||
defer ts.Close()
|
||||
res, err := (&Client{}).FetchCert(context.Background(), ts.URL, false)
|
||||
if err != nil {
|
||||
t.Fatalf("FetchCert: %v", err)
|
||||
}
|
||||
cert := [][]byte{{1}}
|
||||
if !reflect.DeepEqual(res, cert) {
|
||||
t.Errorf("res = %v; want %v", res, cert)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFetchCertRetry(t *testing.T) {
|
||||
var count int
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if count < 1 {
|
||||
w.Header().Set("retry-after", "0")
|
||||
w.WriteHeader(http.StatusAccepted)
|
||||
count++
|
||||
return
|
||||
}
|
||||
w.Write([]byte{1})
|
||||
}))
|
||||
defer ts.Close()
|
||||
res, err := (&Client{}).FetchCert(context.Background(), ts.URL, false)
|
||||
if err != nil {
|
||||
t.Fatalf("FetchCert: %v", err)
|
||||
}
|
||||
cert := [][]byte{{1}}
|
||||
if !reflect.DeepEqual(res, cert) {
|
||||
t.Errorf("res = %v; want %v", res, cert)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFetchCertCancel(t *testing.T) {
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("retry-after", "0")
|
||||
w.WriteHeader(http.StatusAccepted)
|
||||
}))
|
||||
defer ts.Close()
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
done := make(chan struct{})
|
||||
var err error
|
||||
go func() {
|
||||
_, err = (&Client{}).FetchCert(ctx, ts.URL, false)
|
||||
close(done)
|
||||
}()
|
||||
cancel()
|
||||
<-done
|
||||
if err != context.Canceled {
|
||||
t.Errorf("err = %v; want %v", err, context.Canceled)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFetchNonce(t *testing.T) {
|
||||
tests := []struct {
|
||||
code int
|
||||
nonce string
|
||||
}{
|
||||
{http.StatusOK, "nonce1"},
|
||||
{http.StatusBadRequest, "nonce2"},
|
||||
{http.StatusOK, ""},
|
||||
}
|
||||
var i int
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != "HEAD" {
|
||||
t.Errorf("%d: r.Method = %q; want HEAD", i, r.Method)
|
||||
}
|
||||
w.Header().Set("replay-nonce", tests[i].nonce)
|
||||
w.WriteHeader(tests[i].code)
|
||||
}))
|
||||
defer ts.Close()
|
||||
for ; i < len(tests); i++ {
|
||||
test := tests[i]
|
||||
n, err := fetchNonce(http.DefaultClient, ts.URL)
|
||||
if n != test.nonce {
|
||||
t.Errorf("%d: n=%q; want %q", i, n, test.nonce)
|
||||
}
|
||||
switch {
|
||||
case err == nil && test.nonce == "":
|
||||
t.Errorf("%d: n=%q, err=%v; want non-nil error", i, n, err)
|
||||
case err != nil && test.nonce != "":
|
||||
t.Errorf("%d: n=%q, err=%v; want %q", i, n, err, test.nonce)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestLinkHeader(t *testing.T) {
|
||||
h := http.Header{"Link": {
|
||||
`<https://example.com/acme/new-authz>;rel="next"`,
|
||||
`<https://example.com/acme/recover-reg>; rel=recover`,
|
||||
`<https://example.com/acme/terms>; foo=bar; rel="terms-of-service"`,
|
||||
}}
|
||||
tests := []struct{ in, out string }{
|
||||
{"next", "https://example.com/acme/new-authz"},
|
||||
{"recover", "https://example.com/acme/recover-reg"},
|
||||
{"terms-of-service", "https://example.com/acme/terms"},
|
||||
{"empty", ""},
|
||||
}
|
||||
for i, test := range tests {
|
||||
if v := linkHeader(h, test.in); v != test.out {
|
||||
t.Errorf("%d: parseLinkHeader(%q): %q; want %q", i, test.in, v, test.out)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestErrorResponse(t *testing.T) {
|
||||
s := `{
|
||||
"status": 400,
|
||||
"type": "urn:acme:error:xxx",
|
||||
"detail": "text"
|
||||
}`
|
||||
res := &http.Response{
|
||||
StatusCode: 400,
|
||||
Status: "400 Bad Request",
|
||||
Body: ioutil.NopCloser(strings.NewReader(s)),
|
||||
Header: http.Header{"X-Foo": {"bar"}},
|
||||
}
|
||||
err := responseError(res)
|
||||
v, ok := err.(*Error)
|
||||
if !ok {
|
||||
t.Fatalf("err = %+v (%T); want *Error type", err, err)
|
||||
}
|
||||
if v.StatusCode != 400 {
|
||||
t.Errorf("v.StatusCode = %v; want 400", v.StatusCode)
|
||||
}
|
||||
if v.ProblemType != "urn:acme:error:xxx" {
|
||||
t.Errorf("v.ProblemType = %q; want urn:acme:error:xxx", v.ProblemType)
|
||||
}
|
||||
if v.Detail != "text" {
|
||||
t.Errorf("v.Detail = %q; want text", v.Detail)
|
||||
}
|
||||
if !reflect.DeepEqual(v.Header, res.Header) {
|
||||
t.Errorf("v.Header = %+v; want %+v", v.Header, res.Header)
|
||||
}
|
||||
}
|
||||
67
vendor/golang.org/x/crypto/acme/internal/acme/jws.go
generated
vendored
Normal file
67
vendor/golang.org/x/crypto/acme/internal/acme/jws.go
generated
vendored
Normal file
@@ -0,0 +1,67 @@
|
||||
// Copyright 2015 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package acme
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"math/big"
|
||||
)
|
||||
|
||||
// jwsEncodeJSON signs claimset using provided key and a nonce.
|
||||
// The result is serialized in JSON format.
|
||||
// See https://tools.ietf.org/html/rfc7515#section-7.
|
||||
func jwsEncodeJSON(claimset interface{}, key *rsa.PrivateKey, nonce string) ([]byte, error) {
|
||||
jwk := jwkEncode(&key.PublicKey)
|
||||
phead := fmt.Sprintf(`{"alg":"RS256","jwk":%s,"nonce":%q}`, jwk, nonce)
|
||||
phead = base64.RawURLEncoding.EncodeToString([]byte(phead))
|
||||
cs, err := json.Marshal(claimset)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
payload := base64.RawURLEncoding.EncodeToString(cs)
|
||||
h := sha256.New()
|
||||
h.Write([]byte(phead + "." + payload))
|
||||
sig, err := rsa.SignPKCS1v15(rand.Reader, key, crypto.SHA256, h.Sum(nil))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
enc := struct {
|
||||
Protected string `json:"protected"`
|
||||
Payload string `json:"payload"`
|
||||
Sig string `json:"signature"`
|
||||
}{
|
||||
Protected: phead,
|
||||
Payload: payload,
|
||||
Sig: base64.RawURLEncoding.EncodeToString(sig),
|
||||
}
|
||||
return json.Marshal(&enc)
|
||||
}
|
||||
|
||||
// jwkEncode encodes public part of an RSA key into a JWK.
|
||||
// The result is also suitable for creating a JWK thumbprint.
|
||||
func jwkEncode(pub *rsa.PublicKey) string {
|
||||
n := pub.N
|
||||
e := big.NewInt(int64(pub.E))
|
||||
// fields order is important
|
||||
// see https://tools.ietf.org/html/rfc7638#section-3.3 for details
|
||||
return fmt.Sprintf(`{"e":"%s","kty":"RSA","n":"%s"}`,
|
||||
base64.RawURLEncoding.EncodeToString(e.Bytes()),
|
||||
base64.RawURLEncoding.EncodeToString(n.Bytes()),
|
||||
)
|
||||
}
|
||||
|
||||
// JWKThumbprint creates a JWK thumbprint out of pub
|
||||
// as specified in https://tools.ietf.org/html/rfc7638.
|
||||
func JWKThumbprint(pub *rsa.PublicKey) string {
|
||||
jwk := jwkEncode(pub)
|
||||
b := sha256.Sum256([]byte(jwk))
|
||||
return base64.RawURLEncoding.EncodeToString(b[:])
|
||||
}
|
||||
139
vendor/golang.org/x/crypto/acme/internal/acme/jws_test.go
generated
vendored
Normal file
139
vendor/golang.org/x/crypto/acme/internal/acme/jws_test.go
generated
vendored
Normal file
@@ -0,0 +1,139 @@
|
||||
// Copyright 2015 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package acme
|
||||
|
||||
import (
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"encoding/pem"
|
||||
"math/big"
|
||||
"testing"
|
||||
)
|
||||
|
||||
const testKeyPEM = `
|
||||
-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEowIBAAKCAQEA4xgZ3eRPkwoRvy7qeRUbmMDe0V+xH9eWLdu0iheeLlrmD2mq
|
||||
WXfP9IeSKApbn34g8TuAS9g5zhq8ELQ3kmjr+KV86GAMgI6VAcGlq3QrzpTCf/30
|
||||
Ab7+zawrfRaFONa1HwEzPY1KHnGVkxJc85gNkwYI9SY2RHXtvln3zs5wITNrdosq
|
||||
EXeaIkVYBEhbhNu54pp3kxo6TuWLi9e6pXeWetEwmlBwtWZlPoib2j3TxLBksKZf
|
||||
oyFyek380mHgJAumQ/I2fjj98/97mk3ihOY4AgVdCDj1z/GCoZkG5Rq7nbCGyosy
|
||||
KWyDX00Zs+nNqVhoLeIvXC4nnWdJMZ6rogxyQQIDAQABAoIBACIEZTOI1Kao9nmV
|
||||
9IeIsuaR1Y61b9neOF/MLmIVIZu+AAJFCMB4Iw11FV6sFodwpEyeZhx2WkpWVN+H
|
||||
r19eGiLX3zsL0DOdqBJoSIHDWCCMxgnYJ6nvS0nRxX3qVrBp8R2g12Ub+gNPbmFm
|
||||
ecf/eeERIVxfifd9VsyRu34eDEvcmKFuLYbElFcPh62xE3x12UZvV/sN7gXbawpP
|
||||
G+w255vbE5MoaKdnnO83cTFlcHvhn24M/78qP7Te5OAeelr1R89kYxQLpuGe4fbS
|
||||
zc6E3ym5Td6urDetGGrSY1Eu10/8sMusX+KNWkm+RsBRbkyKq72ks/qKpOxOa+c6
|
||||
9gm+Y8ECgYEA/iNUyg1ubRdH11p82l8KHtFC1DPE0V1gSZsX29TpM5jS4qv46K+s
|
||||
8Ym1zmrORM8x+cynfPx1VQZQ34EYeCMIX212ryJ+zDATl4NE0I4muMvSiH9vx6Xc
|
||||
7FmhNnaYzPsBL5Tm9nmtQuP09YEn8poiOJFiDs/4olnD5ogA5O4THGkCgYEA5MIL
|
||||
qWYBUuqbEWLRtMruUtpASclrBqNNsJEsMGbeqBJmoMxdHeSZckbLOrqm7GlMyNRJ
|
||||
Ne/5uWRGSzaMYuGmwsPpERzqEvYFnSrpjW5YtXZ+JtxFXNVfm9Z1gLLgvGpOUCIU
|
||||
RbpoDckDe1vgUuk3y5+DjZihs+rqIJ45XzXTzBkCgYBWuf3segruJZy5rEKhTv+o
|
||||
JqeUvRn0jNYYKFpLBeyTVBrbie6GkbUGNIWbrK05pC+c3K9nosvzuRUOQQL1tJbd
|
||||
4gA3oiD9U4bMFNr+BRTHyZ7OQBcIXdz3t1qhuHVKtnngIAN1p25uPlbRFUNpshnt
|
||||
jgeVoHlsBhApcs5DUc+pyQKBgDzeHPg/+g4z+nrPznjKnktRY1W+0El93kgi+J0Q
|
||||
YiJacxBKEGTJ1MKBb8X6sDurcRDm22wMpGfd9I5Cv2v4GsUsF7HD/cx5xdih+G73
|
||||
c4clNj/k0Ff5Nm1izPUno4C+0IOl7br39IPmfpSuR6wH/h6iHQDqIeybjxyKvT1G
|
||||
N0rRAoGBAKGD+4ZI/E1MoJ5CXB8cDDMHagbE3cq/DtmYzE2v1DFpQYu5I4PCm5c7
|
||||
EQeIP6dZtv8IMgtGIb91QX9pXvP0aznzQKwYIA8nZgoENCPfiMTPiEDT9e/0lObO
|
||||
9XWsXpbSTsRPj0sv1rB+UzBJ0PgjK4q2zOF0sNo7b1+6nlM3BWPx
|
||||
-----END RSA PRIVATE KEY-----
|
||||
`
|
||||
|
||||
// This thumbprint is for the testKey defined above.
|
||||
const testKeyThumbprint = "6nicxzh6WETQlrvdchkz-U3e3DOQZ4heJKU63rfqMqQ"
|
||||
|
||||
var testKey *rsa.PrivateKey
|
||||
|
||||
func init() {
|
||||
d, _ := pem.Decode([]byte(testKeyPEM))
|
||||
if d == nil {
|
||||
panic("no block found in testKeyPEM")
|
||||
}
|
||||
var err error
|
||||
testKey, err = x509.ParsePKCS1PrivateKey(d.Bytes)
|
||||
if err != nil {
|
||||
panic(err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func TestJWSEncodeJSON(t *testing.T) {
|
||||
claims := struct{ Msg string }{"Hello JWS"}
|
||||
// JWS signed with testKey and "nonce" as the nonce value
|
||||
// JSON-serialized JWS fields are split for easier testing
|
||||
const (
|
||||
// {"alg":"RS256","jwk":{"e":"AQAB","kty":"RSA","n":"..."},"nonce":"nonce"}
|
||||
protected = "eyJhbGciOiJSUzI1NiIsImp3ayI6eyJlIjoiQVFBQiIsImt0eSI6" +
|
||||
"IlJTQSIsIm4iOiI0eGdaM2VSUGt3b1J2eTdxZVJVYm1NRGUwVi14" +
|
||||
"SDllV0xkdTBpaGVlTGxybUQybXFXWGZQOUllU0tBcGJuMzRnOFR1" +
|
||||
"QVM5ZzV6aHE4RUxRM2ttanItS1Y4NkdBTWdJNlZBY0dscTNRcnpw" +
|
||||
"VENmXzMwQWI3LXphd3JmUmFGT05hMUh3RXpQWTFLSG5HVmt4SmM4" +
|
||||
"NWdOa3dZSTlTWTJSSFh0dmxuM3pzNXdJVE5yZG9zcUVYZWFJa1ZZ" +
|
||||
"QkVoYmhOdTU0cHAza3hvNlR1V0xpOWU2cFhlV2V0RXdtbEJ3dFda" +
|
||||
"bFBvaWIyajNUeExCa3NLWmZveUZ5ZWszODBtSGdKQXVtUV9JMmZq" +
|
||||
"ajk4Xzk3bWszaWhPWTRBZ1ZkQ0RqMXpfR0NvWmtHNVJxN25iQ0d5" +
|
||||
"b3N5S1d5RFgwMFpzLW5OcVZob0xlSXZYQzRubldkSk1aNnJvZ3h5" +
|
||||
"UVEifSwibm9uY2UiOiJub25jZSJ9"
|
||||
// {"Msg":"Hello JWS"}
|
||||
payload = "eyJNc2ciOiJIZWxsbyBKV1MifQ"
|
||||
signature = "eAGUikStX_UxyiFhxSLMyuyBcIB80GeBkFROCpap2sW3EmkU_ggF" +
|
||||
"knaQzxrTfItICSAXsCLIquZ5BbrSWA_4vdEYrwWtdUj7NqFKjHRa" +
|
||||
"zpLHcoR7r1rEHvkoP1xj49lS5fc3Wjjq8JUhffkhGbWZ8ZVkgPdC" +
|
||||
"4tMBWiQDoth-x8jELP_3LYOB_ScUXi2mETBawLgOT2K8rA0Vbbmx" +
|
||||
"hWNlOWuUf-8hL5YX4IOEwsS8JK_TrTq5Zc9My0zHJmaieqDV0UlP" +
|
||||
"k0onFjPFkGm7MrPSgd0MqRG-4vSAg2O4hDo7rKv4n8POjjXlNQvM" +
|
||||
"9IPLr8qZ7usYBKhEGwX3yq_eicAwBw"
|
||||
)
|
||||
|
||||
b, err := jwsEncodeJSON(claims, testKey, "nonce")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var jws struct{ Protected, Payload, Signature string }
|
||||
if err := json.Unmarshal(b, &jws); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if jws.Protected != protected {
|
||||
t.Errorf("protected:\n%s\nwant:\n%s", jws.Protected, protected)
|
||||
}
|
||||
if jws.Payload != payload {
|
||||
t.Errorf("payload:\n%s\nwant:\n%s", jws.Payload, payload)
|
||||
}
|
||||
if jws.Signature != signature {
|
||||
t.Errorf("signature:\n%s\nwant:\n%s", jws.Signature, signature)
|
||||
}
|
||||
}
|
||||
|
||||
func TestJWKThumbprint(t *testing.T) {
|
||||
// Key example from RFC 7638
|
||||
const base64N = "0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx4cbbfAAt" +
|
||||
"VT86zwu1RK7aPFFxuhDR1L6tSoc_BJECPebWKRXjBZCiFV4n3oknjhMstn6" +
|
||||
"4tZ_2W-5JsGY4Hc5n9yBXArwl93lqt7_RN5w6Cf0h4QyQ5v-65YGjQR0_FD" +
|
||||
"W2QvzqY368QQMicAtaSqzs8KJZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1n9" +
|
||||
"1CbOpbISD08qNLyrdkt-bFTWhAI4vMQFh6WeZu0fM4lFd2NcRwr3XPksINH" +
|
||||
"aQ-G_xBniIqbw0Ls1jF44-csFCur-kEgU8awapJzKnqDKgw"
|
||||
const base64E = "AQAB"
|
||||
const expected = "NzbLsXh8uDCcd-6MNwXF4W_7noWXFZAfHkxZsRGC9Xs"
|
||||
|
||||
bytes, err := base64.RawURLEncoding.DecodeString(base64N)
|
||||
if err != nil {
|
||||
t.Fatalf("Error parsing example key N: %v", err)
|
||||
}
|
||||
n := new(big.Int).SetBytes(bytes)
|
||||
|
||||
bytes, err = base64.RawURLEncoding.DecodeString(base64E)
|
||||
if err != nil {
|
||||
t.Fatalf("Error parsing example key E: %v", err)
|
||||
}
|
||||
e := new(big.Int).SetBytes(bytes)
|
||||
|
||||
pub := &rsa.PublicKey{N: n, E: int(e.Uint64())}
|
||||
th := JWKThumbprint(pub)
|
||||
if th != expected {
|
||||
t.Errorf("th = %q; want %q", th, expected)
|
||||
}
|
||||
}
|
||||
181
vendor/golang.org/x/crypto/acme/internal/acme/types.go
generated
vendored
Normal file
181
vendor/golang.org/x/crypto/acme/internal/acme/types.go
generated
vendored
Normal file
@@ -0,0 +1,181 @@
|
||||
package acme
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// ACME server response statuses used to describe Authorization and Challenge states.
|
||||
const (
|
||||
StatusUnknown = "unknown"
|
||||
StatusPending = "pending"
|
||||
StatusProcessing = "processing"
|
||||
StatusValid = "valid"
|
||||
StatusInvalid = "invalid"
|
||||
StatusRevoked = "revoked"
|
||||
)
|
||||
|
||||
// Account is a user account. It is associated with a private key.
|
||||
type Account struct {
|
||||
// URI is the account unique ID, which is also a URL used to retrieve
|
||||
// account data from the CA.
|
||||
URI string
|
||||
|
||||
// Contact is a slice of contact info used during registration.
|
||||
Contact []string
|
||||
|
||||
// The terms user has agreed to.
|
||||
// Zero value indicates that the user hasn't agreed yet.
|
||||
AgreedTerms string
|
||||
|
||||
// Actual terms of a CA.
|
||||
CurrentTerms string
|
||||
|
||||
// Authz is the authorization URL used to initiate a new authz flow.
|
||||
Authz string
|
||||
|
||||
// Authorizations is a URI from which a list of authorizations
|
||||
// granted to this account can be fetched via a GET request.
|
||||
Authorizations string
|
||||
|
||||
// Certificates is a URI from which a list of certificates
|
||||
// issued for this account can be fetched via a GET request.
|
||||
Certificates string
|
||||
}
|
||||
|
||||
// Directory is ACME server discovery data.
|
||||
type Directory struct {
|
||||
// RegURL is an account endpoint URL, allowing for creating new
|
||||
// and modifying existing accounts.
|
||||
RegURL string
|
||||
|
||||
// AuthzURL is used to initiate Identifier Authorization flow.
|
||||
AuthzURL string
|
||||
|
||||
// CertURL is a new certificate issuance endpoint URL.
|
||||
CertURL string
|
||||
|
||||
// RevokeURL is used to initiate a certificate revocation flow.
|
||||
RevokeURL string
|
||||
|
||||
// Term is a URI identifying the current terms of service.
|
||||
Terms string
|
||||
|
||||
// Website is an HTTP or HTTPS URL locating a website
|
||||
// providing more information about the ACME server.
|
||||
Website string
|
||||
|
||||
// CAA consists of lowercase hostname elements, which the ACME server
|
||||
// recognises as referring to itself for the purposes of CAA record validation
|
||||
// as defined in RFC6844.
|
||||
CAA []string
|
||||
}
|
||||
|
||||
// Challenge encodes a returned CA challenge.
|
||||
type Challenge struct {
|
||||
// Type is the challenge type, e.g. "http-01", "tls-sni-02", "dns-01".
|
||||
Type string
|
||||
|
||||
// URI is where a challenge response can be posted to.
|
||||
URI string
|
||||
|
||||
// Token is a random value that uniquely identifies the challenge.
|
||||
Token string
|
||||
|
||||
// Status identifies the status of this challenge.
|
||||
Status string
|
||||
}
|
||||
|
||||
// Authorization encodes an authorization response.
|
||||
type Authorization struct {
|
||||
// URI uniquely identifies a authorization.
|
||||
URI string
|
||||
|
||||
// Status identifies the status of an authorization.
|
||||
Status string
|
||||
|
||||
// Identifier is what the account is authorized to represent.
|
||||
Identifier AuthzID
|
||||
|
||||
// Challenges that the client needs to fulfill in order to prove possession
|
||||
// of the identifier (for pending authorizations).
|
||||
// For final authorizations, the challenges that were used.
|
||||
Challenges []*Challenge
|
||||
|
||||
// A collection of sets of challenges, each of which would be sufficient
|
||||
// to prove possession of the identifier.
|
||||
// Clients must complete a set of challenges that covers at least one set.
|
||||
// Challenges are identified by their indices in the challenges array.
|
||||
// If this field is empty, the client needs to complete all challenges.
|
||||
Combinations [][]int
|
||||
}
|
||||
|
||||
// AuthzID is an identifier that an account is authorized to represent.
|
||||
type AuthzID struct {
|
||||
Type string // The type of identifier, e.g. "dns".
|
||||
Value string // The identifier itself, e.g. "example.org".
|
||||
}
|
||||
|
||||
// Error is an ACME error, defined in Problem Details for HTTP APIs doc
|
||||
// http://tools.ietf.org/html/draft-ietf-appsawg-http-problem.
|
||||
type Error struct {
|
||||
// StatusCode is The HTTP status code generated by the origin server.
|
||||
StatusCode int
|
||||
// ProblemType is a URI reference that identifies the problem type,
|
||||
// typically in a "urn:acme:error:xxx" form.
|
||||
ProblemType string
|
||||
// Detail is a human-readable explanation specific to this occurrence of the problem.
|
||||
Detail string
|
||||
// Header is the original server error response headers.
|
||||
Header http.Header
|
||||
}
|
||||
|
||||
func (e *Error) Error() string {
|
||||
return fmt.Sprintf("%d %s: %s", e.StatusCode, e.ProblemType, e.Detail)
|
||||
}
|
||||
|
||||
// wireAuthz is ACME JSON representation of Authorization objects.
|
||||
type wireAuthz struct {
|
||||
Status string
|
||||
Challenges []wireChallenge
|
||||
Combinations [][]int
|
||||
Identifier struct {
|
||||
Type string
|
||||
Value string
|
||||
}
|
||||
}
|
||||
|
||||
func (z *wireAuthz) authorization(uri string) *Authorization {
|
||||
a := &Authorization{
|
||||
URI: uri,
|
||||
Status: z.Status,
|
||||
Identifier: AuthzID{Type: z.Identifier.Type, Value: z.Identifier.Value},
|
||||
Combinations: z.Combinations, // shallow copy
|
||||
Challenges: make([]*Challenge, len(z.Challenges)),
|
||||
}
|
||||
for i, v := range z.Challenges {
|
||||
a.Challenges[i] = v.challenge()
|
||||
}
|
||||
return a
|
||||
}
|
||||
|
||||
// wireChallenge is ACME JSON challenge representation.
|
||||
type wireChallenge struct {
|
||||
URI string `json:"uri"`
|
||||
Type string
|
||||
Token string
|
||||
Status string
|
||||
}
|
||||
|
||||
func (c *wireChallenge) challenge() *Challenge {
|
||||
v := &Challenge{
|
||||
URI: c.URI,
|
||||
Type: c.Type,
|
||||
Token: c.Token,
|
||||
Status: c.Status,
|
||||
}
|
||||
if v.Status == "" {
|
||||
v.Status = StatusPending
|
||||
}
|
||||
return v
|
||||
}
|
||||
Reference in New Issue
Block a user