package acme import ( "crypto" "crypto/ecdsa" "crypto/rsa" "encoding/base64" "errors" "io/ioutil" "net/http" "strings" "github.com/square/go-jose" ) // KeySize is a default RSA key size const KeySize = 2048 var ErrNoNonces = errors.New("out of nonces") type signer struct { jose.Signer nonces chan string } func Thumbnail(privKey crypto.PrivateKey) (string, error) { thumbnail := func(pubKey crypto.PublicKey) (string, error) { jwk := &jose.JsonWebKey{Key: pubKey} t, err := jwk.Thumbprint(crypto.SHA256) if err != nil { return "", err } return base64.RawURLEncoding.EncodeToString(t), nil } switch k := privKey.(type) { case *rsa.PrivateKey: return thumbnail(k.Public()) case *ecdsa.PrivateKey: return thumbnail(k.Public()) } return "", ErrKeyType } func newSigner(privKey crypto.PrivateKey) (*signer, error) { js := func(crypto.PrivateKey) (jose.Signer, error) { switch k := privKey.(type) { case *rsa.PrivateKey: return jose.NewSigner(jose.RS256, k) case *ecdsa.PrivateKey: return jose.NewSigner(jose.ES384, k) } return nil, ErrKeyType } s, err := js(privKey) if err != nil { return nil, err } sig := &signer{Signer: s, nonces: make(chan string, 100)} sig.SetNonceSource(sig) return sig, nil } // Nonce implements jose nonce provider func (s signer) Nonce() (string, error) { select { case nonce := <-s.nonces: return nonce, nil default: return "", ErrNoNonces } } // RoundTrip extracts nonces from HTTP response func (s signer) RoundTrip(req *http.Request) (*http.Response, error) { if req.Method == http.MethodPost { body, err := ioutil.ReadAll(req.Body) if err != nil { return nil, err } req.Body.Close() obj, err := s.Sign(body) if err != nil { return nil, err } signed := obj.FullSerialize() req.ContentLength = int64(len(signed)) req.Body = ioutil.NopCloser(strings.NewReader(signed)) } resp, err := http.DefaultTransport.RoundTrip(req) if err != nil { return nil, err } if nonce := resp.Header.Get("Replay-Nonce"); nonce != "" { select { case s.nonces <- nonce: default: } } return resp, nil }