Skip to content

Commit

Permalink
Move fetching new nonces from nonce stack into the client
Browse files Browse the repository at this point in the history
  • Loading branch information
eggsampler committed Aug 2, 2018
1 parent 2afc4bb commit 84e95c3
Show file tree
Hide file tree
Showing 3 changed files with 40 additions and 73 deletions.
48 changes: 35 additions & 13 deletions acme.go
Expand Up @@ -13,6 +13,7 @@ import (

"bytes"
"crypto"
"errors"
)

const (
Expand All @@ -34,16 +35,14 @@ func NewClient(directoryURL string, options ...OptionFunc) (Client, error) {
// can be overridden via OptionFunc eg: acme.NewClient(url, WithHTTPTimeout(10 * time.Second))
httpClient.Timeout = 60 * time.Second

ns := &nonceStack{
client: httpClient,
}

acmeClient := Client{
httpClient: httpClient,
nonces: ns,
nonces: &nonceStack{},
retryCount: 5,
}

acmeClient.dir.URL = directoryURL

for _, opt := range options {
if err := opt(&acmeClient); err != nil {
return acmeClient, fmt.Errorf("acme: error setting option: %v", err)
Expand All @@ -54,9 +53,6 @@ func NewClient(directoryURL string, options ...OptionFunc) (Client, error) {
return acmeClient, err
}

acmeClient.dir.URL = directoryURL
ns.newNonceURL = acmeClient.dir.NewNonce

return acmeClient, nil
}

Expand All @@ -80,7 +76,7 @@ func (c Client) getPollingDurations() (time.Duration, time.Duration) {

// Helper function to have a central point for performing http requests.
// Stores any returned nonces in the stack.
func (c Client) do(req *http.Request) (*http.Response, error) {
func (c Client) do(req *http.Request, addNonce bool) (*http.Response, error) {
// More details: https://tools.ietf.org/html/draft-ietf-acme-acme-10#section-6.1
// identifier for this client, as well as the default go user agent
if c.userAgentSuffix != "" {
Expand All @@ -98,7 +94,9 @@ func (c Client) do(req *http.Request) (*http.Response, error) {
return resp, err
}

c.nonces.push(resp.Header.Get("Replay-Nonce"))
if addNonce {
c.nonces.push(resp.Header.Get("Replay-Nonce"))
}

return resp, nil
}
Expand All @@ -110,7 +108,7 @@ func (c Client) getRaw(url string, expectedStatus ...int) (*http.Response, []byt
return nil, nil, fmt.Errorf("acme: error creating request: %v", err)
}

resp, err := c.do(req)
resp, err := c.do(req, true)
if err != nil {
return resp, nil, fmt.Errorf("acme: error fetching response: %v", err)
}
Expand Down Expand Up @@ -144,10 +142,34 @@ func (c Client) get(url string, out interface{}, expectedStatus ...int) (*http.R
return resp, nil
}

func (c Client) nonce() (string, error) {
nonce := c.nonces.pop()
if nonce != "" {
return nonce, nil
}

if c.dir.NewNonce == "" {
return "", errors.New("acme: no new nonce url")
}

req, err := http.NewRequest("HEAD", c.dir.NewNonce, nil)
if err != nil {
return "", fmt.Errorf("acme: error creating new nonce request: %v", err)
}

resp, err := c.do(req, false)
if err != nil {
return "", fmt.Errorf("acme: error fetching new nonce: %v", err)
}

nonce = resp.Header.Get("Replay-Nonce")
return nonce, nil
}

// Helper function to perform an http post request and read the body.
// Will attempt to retry if error is badNonce
func (c Client) postRaw(retryCount int, requestURL, keyID string, privateKey crypto.Signer, payload interface{}, out interface{}, expectedStatus []int) (*http.Response, []byte, error) {
nonce, err := c.nonces.Nonce()
nonce, err := c.nonce()
if err != nil {
return nil, nil, err
}
Expand All @@ -163,7 +185,7 @@ func (c Client) postRaw(retryCount int, requestURL, keyID string, privateKey cry
}
req.Header.Set("Content-Type", "application/jose+json")

resp, err := c.do(req)
resp, err := c.do(req, true)
if err != nil {
return resp, nil, fmt.Errorf("acme: error sending request: %v", err)
}
Expand Down
36 changes: 0 additions & 36 deletions nonce.go
@@ -1,19 +1,13 @@
package acme

import (
"errors"
"fmt"
"net/http"
"sync"
)

// Simple thread-safe stack impl
type nonceStack struct {
lock sync.Mutex
stack []string

client *http.Client
newNonceURL string
}

// Pushes a nonce to the stack.
Expand Down Expand Up @@ -49,33 +43,3 @@ func (ns *nonceStack) pop() string {

return v
}

// Used to insert a nonce field into a jws header.
func (ns *nonceStack) Nonce() (string, error) {
nonce := ns.pop()
if nonce != "" {
return nonce, nil
}

if ns.newNonceURL == "" {
return "", errors.New("acme: no newNonce url")
}

req, err := http.NewRequest("HEAD", ns.newNonceURL, nil)
if err != nil {
return "", fmt.Errorf("acme: error creating newNonce request: %v", err)
}
req.Header.Set("User-Agent", userAgentString)

resp, err := ns.client.Head(ns.newNonceURL)
if err != nil {
return "", fmt.Errorf("acme: error fetching new nonce: %v", err)
}

replaceNonce := resp.Header.Get("Replay-Nonce")
if replaceNonce == "" {
return "", errors.New("acme: no nonce sent")
}

return replaceNonce, nil
}
29 changes: 5 additions & 24 deletions nonce_test.go
@@ -1,45 +1,26 @@
package acme

import (
"net/http"
"testing"
)

func TestNonceStack_Nonce(t *testing.T) {
ns := nonceStack{
client: http.DefaultClient,
}
func TestNonceStack(t *testing.T) {
ns := nonceStack{}

ns.push("test")
if len(ns.stack) != 1 {
t.Fatalf("expected stack size of 1, got: %d", len(ns.stack))
}

nonce, err := ns.Nonce()
if err != nil {
t.Fatalf("unexpected error popping nonce from stack: %v", err)
}
nonce := ns.pop()
if nonce != "test" {
t.Fatalf("bad nonce returned from stack, expected %q got %q", "test", nonce)
}

if _, err = ns.Nonce(); err == nil {
t.Fatal("expected error, got none")
}

ns.newNonceURL = "http://google.com/"
if _, err = ns.Nonce(); err == nil {
t.Fatal("expected error, got none")
if nonce := ns.pop(); nonce != "" {
t.Fatalf("expected no nonce, got: %v", nonce)
}

ns.newNonceURL = testClient.Directory().NewNonce
nonce, err = ns.Nonce()
if err != nil {
t.Fatalf("expected no error, got: %v", err)
}
if nonce == "" {
t.Fatal("no nonce returned")
}
if len(ns.stack) != 0 {
t.Fatal("expected empty stack")
}
Expand Down

0 comments on commit 84e95c3

Please sign in to comment.