Skip to content
Snippets Groups Projects
http_challenge.go 1.17 KiB
Newer Older
ale's avatar
ale committed
package acmeserver

import (
	"context"
	"net/http"
	"sync"

	"golang.org/x/crypto/acme"
)

// httpValidator can validate http-01 challenges. It serves
// validation tokens under the /.well-known/acme-challenge path when
// used as an HTTP handler.
type httpValidator struct {
	mx     sync.Mutex
	tokens map[string][]byte
}

func newHTTPValidator() *httpValidator {
	return &httpValidator{
		tokens: make(map[string][]byte),
	}
}

func (h *httpValidator) Fulfill(_ context.Context, client *acme.Client, _ string, chal *acme.Challenge) (func(), error) {
	resp, err := client.HTTP01ChallengeResponse(chal.Token)
	if err != nil {
		return nil, err
	}
	path := client.HTTP01ChallengePath(chal.Token)

	h.mx.Lock()
	h.tokens[path] = []byte(resp)
	h.mx.Unlock()

	return func() {
		go func() {
			h.mx.Lock()
			delete(h.tokens, path)
			h.mx.Unlock()
		}()
	}, nil
}

func (h *httpValidator) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	// No defer because it's not polite to Write() while holding the lock.
	h.mx.Lock()
	data, ok := h.tokens[r.URL.Path]
	h.mx.Unlock()

	if !ok {
		http.NotFound(w, r)
		return
	}

	// Not interested in errors writing the response.
	w.Write(data) // nolint
}