lego/acme/jws.go
Manuel Valls Fernández a111d61d85 Move nonce retry from jws to http (#367)
* Move nonce retry from jws to http

The error raised by an "invalid nonce" response never appeared
inside jws.go, but instead it was handled at http.go, so it makes
sense to move the retry logic to that file. The previous code from
jws.go had no effect and did not solve issues related to invalid
nonces.

* Rename retry response variable name for clarity
2017-03-30 02:25:34 +02:00

131 lines
2.7 KiB
Go

package acme
import (
"bytes"
"crypto"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rsa"
"fmt"
"net/http"
"sync"
"gopkg.in/square/go-jose.v1"
)
type jws struct {
directoryURL string
privKey crypto.PrivateKey
nonces nonceManager
}
func keyAsJWK(key interface{}) *jose.JsonWebKey {
switch k := key.(type) {
case *ecdsa.PublicKey:
return &jose.JsonWebKey{Key: k, Algorithm: "EC"}
case *rsa.PublicKey:
return &jose.JsonWebKey{Key: k, Algorithm: "RSA"}
default:
return nil
}
}
// Posts a JWS signed message to the specified URL.
// It does NOT close the response body, so the caller must
// do that if no error was returned.
func (j *jws) post(url string, content []byte) (*http.Response, error) {
signedContent, err := j.signContent(content)
if err != nil {
return nil, fmt.Errorf("Failed to sign content -> %s", err.Error())
}
resp, err := httpPost(url, "application/jose+json", bytes.NewBuffer([]byte(signedContent.FullSerialize())))
if err != nil {
return nil, fmt.Errorf("Failed to HTTP POST to %s -> %s", url, err.Error())
}
nonce, nonceErr := getNonceFromResponse(resp)
if nonceErr == nil {
j.nonces.Push(nonce)
}
return resp, nil
}
func (j *jws) signContent(content []byte) (*jose.JsonWebSignature, error) {
var alg jose.SignatureAlgorithm
switch k := j.privKey.(type) {
case *rsa.PrivateKey:
alg = jose.RS256
case *ecdsa.PrivateKey:
if k.Curve == elliptic.P256() {
alg = jose.ES256
} else if k.Curve == elliptic.P384() {
alg = jose.ES384
}
}
signer, err := jose.NewSigner(alg, j.privKey)
if err != nil {
return nil, fmt.Errorf("Failed to create jose signer -> %s", err.Error())
}
signer.SetNonceSource(j)
signed, err := signer.Sign(content)
if err != nil {
return nil, fmt.Errorf("Failed to sign content -> %s", err.Error())
}
return signed, nil
}
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 "", fmt.Errorf("Failed to get nonce from HTTP HEAD -> %s", err.Error())
}
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
}