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"
|
"net/http/httptest"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestNewClient(t *testing.T) {
|
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) {
|
func TestValidate(t *testing.T) {
|
||||||
var statuses []string
|
var statuses []string
|
||||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
82
acme/jws.go
82
acme/jws.go
|
@ -16,8 +16,7 @@ import (
|
||||||
type jws struct {
|
type jws struct {
|
||||||
directoryURL string
|
directoryURL string
|
||||||
privKey crypto.PrivateKey
|
privKey crypto.PrivateKey
|
||||||
nonces []string
|
nonces nonceManager
|
||||||
sync.Mutex
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func keyAsJWK(key interface{}) *jose.JsonWebKey {
|
func keyAsJWK(key interface{}) *jose.JsonWebKey {
|
||||||
|
@ -46,9 +45,10 @@ func (j *jws) post(url string, content []byte) (*http.Response, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
j.Lock()
|
nonce, nonceErr := getNonceFromResponse(resp)
|
||||||
defer j.Unlock()
|
if nonceErr == nil {
|
||||||
j.getNonceFromResponse(resp)
|
j.nonces.Push(nonce)
|
||||||
|
}
|
||||||
|
|
||||||
return resp, err
|
return resp, err
|
||||||
}
|
}
|
||||||
|
@ -80,38 +80,52 @@ func (j *jws) signContent(content []byte) (*jose.JsonWebSignature, error) {
|
||||||
return signed, nil
|
return signed, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (j *jws) getNonceFromResponse(resp *http.Response) error {
|
func (j *jws) Nonce() (string, error) {
|
||||||
|
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")
|
nonce := resp.Header.Get("Replay-Nonce")
|
||||||
if nonce == "" {
|
if nonce == "" {
|
||||||
return fmt.Errorf("Server did not respond with a proper nonce header.")
|
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]
|
|
||||||
return nonce, nil
|
return nonce, nil
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue