forked from TrueCloudLab/lego
Reduce nonce locking (#340)
* [reduce-locking] Prepare for change * [reduce-locking] Do not lock on http request * [reduce-locking] Move getNonce and getNonceFromResponse from jws struct cause they do not need access to it * [reduce-locking] Extract nonceManager * [reduce-locking] Add test that tries to show locking on http requests problem
This commit is contained in:
parent
be23e242c1
commit
09d8a49bf2
2 changed files with 82 additions and 34 deletions
|
@ -10,6 +10,7 @@ import (
|
|||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestNewClient(t *testing.T) {
|
||||
|
@ -118,6 +119,39 @@ func TestClientOptPort(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestNotHoldingLockWhileMakingHTTPRequests(t *testing.T) {
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
time.Sleep(250 * time.Millisecond)
|
||||
w.Header().Add("Replay-Nonce", "12345")
|
||||
w.Header().Add("Retry-After", "0")
|
||||
writeJSONResponse(w, &challenge{Type: "http-01", Status: "Valid", URI: "http://example.com/", Token: "token"})
|
||||
}))
|
||||
defer ts.Close()
|
||||
|
||||
privKey, _ := rsa.GenerateKey(rand.Reader, 512)
|
||||
j := &jws{privKey: privKey, directoryURL: ts.URL}
|
||||
ch := make(chan bool)
|
||||
resultCh := make(chan bool)
|
||||
go func() {
|
||||
j.Nonce()
|
||||
ch <- true
|
||||
}()
|
||||
go func() {
|
||||
j.Nonce()
|
||||
ch <- true
|
||||
}()
|
||||
go func() {
|
||||
<-ch
|
||||
<-ch
|
||||
resultCh <- true
|
||||
}()
|
||||
select {
|
||||
case <-resultCh:
|
||||
case <-time.After(400 * time.Millisecond):
|
||||
t.Fatal("JWS is probably holding a lock while making HTTP request")
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidate(t *testing.T) {
|
||||
var statuses []string
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
|
|
88
acme/jws.go
88
acme/jws.go
|
@ -16,8 +16,7 @@ import (
|
|||
type jws struct {
|
||||
directoryURL string
|
||||
privKey crypto.PrivateKey
|
||||
nonces []string
|
||||
sync.Mutex
|
||||
nonces nonceManager
|
||||
}
|
||||
|
||||
func keyAsJWK(key interface{}) *jose.JsonWebKey {
|
||||
|
@ -46,9 +45,10 @@ func (j *jws) post(url string, content []byte) (*http.Response, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
j.Lock()
|
||||
defer j.Unlock()
|
||||
j.getNonceFromResponse(resp)
|
||||
nonce, nonceErr := getNonceFromResponse(resp)
|
||||
if nonceErr == nil {
|
||||
j.nonces.Push(nonce)
|
||||
}
|
||||
|
||||
return resp, err
|
||||
}
|
||||
|
@ -80,38 +80,52 @@ func (j *jws) signContent(content []byte) (*jose.JsonWebSignature, error) {
|
|||
return signed, nil
|
||||
}
|
||||
|
||||
func (j *jws) getNonceFromResponse(resp *http.Response) error {
|
||||
nonce := resp.Header.Get("Replay-Nonce")
|
||||
if nonce == "" {
|
||||
return fmt.Errorf("Server did not respond with a proper nonce header.")
|
||||
}
|
||||
|
||||
j.nonces = append(j.nonces, nonce)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (j *jws) getNonce() error {
|
||||
resp, err := httpHead(j.directoryURL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return j.getNonceFromResponse(resp)
|
||||
}
|
||||
|
||||
func (j *jws) Nonce() (string, error) {
|
||||
j.Lock()
|
||||
defer j.Unlock()
|
||||
nonce := ""
|
||||
if len(j.nonces) == 0 {
|
||||
err := j.getNonce()
|
||||
if err != nil {
|
||||
return nonce, err
|
||||
}
|
||||
}
|
||||
if len(j.nonces) == 0 {
|
||||
return "", fmt.Errorf("Can't get nonce")
|
||||
}
|
||||
nonce, j.nonces = j.nonces[len(j.nonces)-1], j.nonces[:len(j.nonces)-1]
|
||||
if nonce, ok := j.nonces.Pop(); ok {
|
||||
return nonce, nil
|
||||
}
|
||||
|
||||
return getNonce(j.directoryURL)
|
||||
}
|
||||
|
||||
type nonceManager struct {
|
||||
nonces []string
|
||||
sync.Mutex
|
||||
}
|
||||
|
||||
func (n *nonceManager) Pop() (string, bool) {
|
||||
n.Lock()
|
||||
defer n.Unlock()
|
||||
|
||||
if len(n.nonces) == 0 {
|
||||
return "", false
|
||||
}
|
||||
|
||||
nonce := n.nonces[len(n.nonces)-1]
|
||||
n.nonces = n.nonces[:len(n.nonces)-1]
|
||||
return nonce, true
|
||||
}
|
||||
|
||||
func (n *nonceManager) Push(nonce string) {
|
||||
n.Lock()
|
||||
defer n.Unlock()
|
||||
n.nonces = append(n.nonces, nonce)
|
||||
}
|
||||
|
||||
func getNonce(url string) (string, error) {
|
||||
resp, err := httpHead(url)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return getNonceFromResponse(resp)
|
||||
}
|
||||
|
||||
func getNonceFromResponse(resp *http.Response) (string, error) {
|
||||
nonce := resp.Header.Get("Replay-Nonce")
|
||||
if nonce == "" {
|
||||
return "", fmt.Errorf("Server did not respond with a proper nonce header.")
|
||||
}
|
||||
|
||||
return nonce, nil
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue