package acme import ( "net/http" "strings" "sync" ) // ChallengeResponder serves HTTP-01 challenge responses at // /.well-known/acme-challenge/{token} — mount it on the plain :80 listener // (or wherever the CA's HTTP-01 validator will connect) before requesting // challenge validation. type ChallengeResponder struct { mu sync.RWMutex tokens map[string]string // token -> key authorization } func NewChallengeResponder() *ChallengeResponder { return &ChallengeResponder{tokens: make(map[string]string)} } func (c *ChallengeResponder) Set(token, keyAuthorization string) { c.mu.Lock() defer c.mu.Unlock() c.tokens[token] = keyAuthorization } func (c *ChallengeResponder) Remove(token string) { c.mu.Lock() defer c.mu.Unlock() delete(c.tokens, token) } func (c *ChallengeResponder) ServeHTTP(w http.ResponseWriter, r *http.Request) { token := strings.TrimPrefix(r.URL.Path, "/.well-known/acme-challenge/") c.mu.RLock() keyAuth, ok := c.tokens[token] c.mu.RUnlock() if !ok { http.NotFound(w, r) return } w.Header().Set("Content-Type", "application/octet-stream") w.Write([]byte(keyAuth)) }